11import TPEN from "../../../api/TPEN.js"
2+ import { CleanupRegistry } from '../../../utilities/CleanupRegistry.js'
23
34/**
45 * TpenHeader - Main site header with navigation, title, and action button.
56 * @element tpen-header
67 */
78class TpenHeader extends HTMLElement {
9+ /** @type {CleanupRegistry } Registry for cleanup handlers */
10+ cleanup = new CleanupRegistry ( )
811 /** @type {Function|null } Handler for title events */
912 _titleHandler = null
1013 /** @type {Function|null } Handler for action link events */
1114 _actionLinkHandler = null
1215 /** @type {Function|null } Handler for action link remove events */
1316 _actionLinkRemoveHandler = null
17+ /** @type {Function|null } Handler for logout button */
18+ _logoutHandler = null
1419
1520 constructor ( ) {
1621 super ( ) ;
@@ -60,6 +65,8 @@ class TpenHeader extends HTMLElement {
6065 ` ;
6166 }
6267 connectedCallback ( ) {
68+ TPEN . attachAuthentication ( this )
69+
6370 this . _titleHandler = ev => {
6471 const title = this . shadowRoot . querySelector ( '.banner' )
6572 if ( ! ev . detail ) {
@@ -82,23 +89,18 @@ class TpenHeader extends HTMLElement {
8289 btn . removeEventListener ( 'click' , ev . detail . callback )
8390 }
8491
85- TPEN . eventDispatcher . on ( 'tpen-gui-title' , this . _titleHandler )
86- TPEN . eventDispatcher . on ( 'tpen-gui-action-link' , this . _actionLinkHandler )
87- TPEN . eventDispatcher . on ( 'tpen-gui-action-link-remove' , this . _actionLinkRemoveHandler )
92+ this . cleanup . onEvent ( TPEN . eventDispatcher , 'tpen-gui-title' , this . _titleHandler )
93+ this . cleanup . onEvent ( TPEN . eventDispatcher , 'tpen-gui-action-link' , this . _actionLinkHandler )
94+ this . cleanup . onEvent ( TPEN . eventDispatcher , 'tpen-gui-action-link-remove' , this . _actionLinkRemoveHandler )
8895
8996 this . _logoutHandler = ( ) => TPEN . logout ( )
90- this . shadowRoot . querySelector ( '.logout-btn' ) . addEventListener ( 'click' , this . _logoutHandler )
97+ const logoutBtn = this . shadowRoot . querySelector ( '.logout-btn' )
98+ this . cleanup . onElement ( logoutBtn , 'click' , this . _logoutHandler )
9199 this . setupDraggableButton ( )
92100 }
93101
94102 disconnectedCallback ( ) {
95- if ( this . _titleHandler ) TPEN . eventDispatcher . off ( 'tpen-gui-title' , this . _titleHandler )
96- if ( this . _actionLinkHandler ) TPEN . eventDispatcher . off ( 'tpen-gui-action-link' , this . _actionLinkHandler )
97- if ( this . _actionLinkRemoveHandler ) TPEN . eventDispatcher . off ( 'tpen-gui-action-link-remove' , this . _actionLinkRemoveHandler )
98- const logoutBtn = this . shadowRoot ?. querySelector ( '.logout-btn' )
99- if ( logoutBtn && this . _logoutHandler ) {
100- logoutBtn . removeEventListener ( 'click' , this . _logoutHandler )
101- }
103+ this . cleanup . run ( )
102104 }
103105
104106 setupDraggableButton ( ) {
@@ -126,23 +128,23 @@ class TpenHeader extends HTMLElement {
126128 btn . style . left = '0px'
127129 initialRect = btn . getBoundingClientRect ( )
128130 }
129-
131+
130132 const header = this . shadowRoot . querySelector ( 'header' )
131133 const headerRect = header . getBoundingClientRect ( )
132134 const btnWidth = initialRect . width
133-
135+
134136 // Calculate bounds relative to initial position
135137 const maxLeft = headerRect . right - initialRect . right - 20 // Space to right edge
136138 const maxRight = headerRect . left - initialRect . left + 20 // Space to left edge
137-
139+
138140 return { maxLeft, maxRight }
139141 }
140142
141143 const animate = ( ) => {
142144 if ( Math . abs ( velocityX ) > MIN_VELOCITY ) {
143145 currentX += velocityX
144146 velocityX *= FRICTION
145-
147+
146148 // Check boundaries and bounce
147149 const bounds = getBounds ( )
148150 if ( currentX > bounds . maxLeft ) {
@@ -152,7 +154,7 @@ class TpenHeader extends HTMLElement {
152154 currentX = bounds . maxRight
153155 velocityX = Math . abs ( velocityX ) * BOUNCE_DAMPING // Bounce back with damping
154156 }
155-
157+
156158 btn . style . left = `${ currentX } px`
157159 animationFrame = requestAnimationFrame ( animate )
158160 } else {
@@ -166,7 +168,7 @@ class TpenHeader extends HTMLElement {
166168 cancelAnimationFrame ( animationFrame )
167169 animationFrame = null
168170 }
169-
171+
170172 isDragging = true
171173 hasMoved = false
172174 dragStartTime = Date . now ( )
@@ -182,24 +184,24 @@ class TpenHeader extends HTMLElement {
182184
183185 const onPointerMove = ( e ) => {
184186 if ( ! isDragging ) return
185-
187+
186188 const now = Date . now ( )
187189 const deltaTime = now - lastTime
188190 const deltaX = e . clientX - startX
189-
191+
190192 if ( Math . abs ( deltaX - currentX ) > DRAG_THRESHOLD ) {
191193 hasMoved = true
192194 }
193-
195+
194196 // Calculate velocity for momentum
195197 if ( deltaTime > 0 ) {
196198 velocityX = ( e . clientX - lastX ) / deltaTime * 16 // Normalize to ~60fps
197199 }
198-
200+
199201 // Constrain to viewport bounds while dragging
200202 const bounds = getBounds ( )
201203 currentX = Math . max ( bounds . maxRight , Math . min ( bounds . maxLeft , deltaX ) )
202-
204+
203205 lastX = e . clientX
204206 lastTime = now
205207 btn . style . position = 'relative'
@@ -208,39 +210,48 @@ class TpenHeader extends HTMLElement {
208210
209211 const onPointerUp = ( e ) => {
210212 if ( ! isDragging ) return
211-
213+
212214 isDragging = false
213215 btn . style . cursor = 'grab'
214216 btn . releasePointerCapture ( e . pointerId )
215-
217+
216218 const dragDuration = Date . now ( ) - dragStartTime
217-
219+
218220 // If the button was dragged significantly (distance or time), prevent the click
219221 if ( hasMoved || dragDuration > TIME_THRESHOLD ) {
220222 e . preventDefault ( )
221223 e . stopPropagation ( )
222224 }
223-
225+
224226 // Apply momentum if there's velocity
225227 if ( Math . abs ( velocityX ) > MIN_VELOCITY && hasMoved ) {
226228 animationFrame = requestAnimationFrame ( animate )
227229 }
228230 }
229231
230232 // Prevent click if drag occurred
231- btn . addEventListener ( 'click' , ( e ) => {
233+ const clickHandler = ( e ) => {
232234 if ( hasMoved ) {
233235 e . preventDefault ( )
234236 e . stopPropagation ( )
235237 }
236- } , true )
238+ }
237239
238- btn . addEventListener ( 'pointerdown' , onPointerDown )
239- btn . addEventListener ( 'pointermove' , onPointerMove )
240- btn . addEventListener ( 'pointerup' , onPointerUp )
241- btn . addEventListener ( 'pointercancel' , onPointerUp )
240+ // Register all button event listeners with cleanup registry
241+ this . cleanup . onElement ( btn , 'click' , clickHandler , true )
242+ this . cleanup . onElement ( btn , 'pointerdown' , onPointerDown )
243+ this . cleanup . onElement ( btn , 'pointermove' , onPointerMove )
244+ this . cleanup . onElement ( btn , 'pointerup' , onPointerUp )
245+ this . cleanup . onElement ( btn , 'pointercancel' , onPointerUp )
242246 btn . style . cursor = 'grab'
243247 btn . style . touchAction = 'none'
248+
249+ // Track animation frame for cleanup
250+ this . cleanup . add ( ( ) => {
251+ if ( animationFrame ) {
252+ cancelAnimationFrame ( animationFrame )
253+ }
254+ } )
244255 }
245256}
246257
0 commit comments