Skip to content

Commit f46317d

Browse files
committed
Fix issues found in Copilot review iteration 4
1 parent d9350a3 commit f46317d

2 files changed

Lines changed: 63 additions & 26 deletions

File tree

src/utils/events.js

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import DOMHelper from '../helpers/dom.js'
77

8-
class EventHelper {
8+
export class EventHelper {
99
/**
1010
* Add event listener with namespace support
1111
* @param {Element|string} element - DOM element or selector
@@ -270,36 +270,73 @@ class EventHelper {
270270
*/
271271
static offAll (element, namespace) {
272272
if (typeof element === 'string') element = DOMHelper.$(element)
273-
if (!element || !element._eventHandlers) return element
273+
if (!element) return element
274+
275+
// Clean up non-delegated event handlers first
276+
if (element._eventHandlers) {
277+
// Iterate through all event types
278+
Object.keys(element._eventHandlers).forEach(eventType => {
279+
if (namespace) {
280+
// Remove only handlers matching the namespace
281+
element._eventHandlers[eventType] = element._eventHandlers[eventType].filter(info => {
282+
if (info && info.namespace === namespace) {
283+
element.removeEventListener(eventType, info.wrappedHandler, info.options)
284+
return false
285+
}
286+
return true
287+
})
288+
} else {
289+
// Remove all handlers for this event type
290+
element._eventHandlers[eventType].forEach(info => {
291+
if (info) {
292+
element.removeEventListener(eventType, info.wrappedHandler, info.options)
293+
}
294+
})
295+
element._eventHandlers[eventType] = []
296+
}
274297

275-
// Iterate through all event types
276-
Object.keys(element._eventHandlers).forEach(eventType => {
298+
// Clean up empty event type arrays
299+
if (!element._eventHandlers[eventType] || element._eventHandlers[eventType].length === 0) {
300+
delete element._eventHandlers[eventType]
301+
}
302+
})
303+
304+
// Clean up if no handlers left at all
305+
if (Object.keys(element._eventHandlers).length === 0) {
306+
delete element._eventHandlers
307+
}
308+
}
309+
310+
// Also clean up any delegated handlers tied to this element
311+
if (element._delegates) {
277312
if (namespace) {
278-
// Remove only handlers matching the namespace
279-
element._eventHandlers[eventType] = element._eventHandlers[eventType].filter(info => {
280-
if (info.namespace === namespace) {
281-
element.removeEventListener(eventType, info.wrappedHandler, info.options)
313+
// Remove only delegates matching the namespace
314+
element._delegates = element._delegates.filter(delegate => {
315+
if (delegate.namespace === namespace) {
316+
// Remove the underlying event listener
317+
const eventWithNamespace = delegate.namespace
318+
? `${delegate.eventType}.${delegate.namespace}`
319+
: delegate.eventType
320+
this.off(element, eventWithNamespace, delegate.handler)
282321
return false
283322
}
284323
return true
285324
})
286325
} else {
287-
// Remove all handlers for this event type
288-
element._eventHandlers[eventType].forEach(info => {
289-
element.removeEventListener(eventType, info.wrappedHandler, info.options)
326+
// Remove all delegates
327+
element._delegates.forEach(delegate => {
328+
const eventWithNamespace = delegate.namespace
329+
? `${delegate.eventType}.${delegate.namespace}`
330+
: delegate.eventType
331+
this.off(element, eventWithNamespace, delegate.handler)
290332
})
291-
element._eventHandlers[eventType] = []
333+
element._delegates = []
292334
}
293335

294-
// Clean up empty event type arrays
295-
if (element._eventHandlers[eventType].length === 0) {
296-
delete element._eventHandlers[eventType]
336+
// Clean up delegates storage if no delegates remain
337+
if (element._delegates.length === 0) {
338+
delete element._delegates
297339
}
298-
})
299-
300-
// Clean up if no handlers left at all
301-
if (Object.keys(element._eventHandlers).length === 0) {
302-
delete element._eventHandlers
303340
}
304341

305342
return element
@@ -308,7 +345,9 @@ class EventHelper {
308345
/**
309346
* Native DOM helper methods
310347
*/
311-
static $ = DOMHelper.$.bind(DOMHelper)
348+
static $ (...args) {
349+
return DOMHelper.$.apply(DOMHelper, args)
350+
}
312351

313352
/**
314353
* Find parent element matching selector
@@ -344,7 +383,7 @@ class EventHelper {
344383
* Get data attribute value
345384
* @param {Element|string} element - DOM element or selector
346385
* @param {string} key - Data key
347-
* @returns {string|null} The data attribute value or null
386+
* @returns {string|undefined|null} The data attribute value, undefined if the key is missing, or null if the element is not found
348387
*/
349388
static getData (element, key) {
350389
if (typeof element === 'string') element = DOMHelper.$(element)
@@ -368,5 +407,3 @@ class EventHelper {
368407
return element
369408
}
370409
}
371-
372-
export default EventHelper

tests/utils/events.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
import { beforeEach, describe, expect, it } from 'vitest'
6-
import EventHelper from '@/utils/events.js'
6+
import { EventHelper } from '@/utils/events.js'
77
import DOMHelper from '@/helpers/dom.js'
88

99
// Setup test environment
@@ -319,7 +319,7 @@ describe('EventHelper', () => {
319319
expect(EventHelper.trigger(null, 'click')).toBeNull()
320320
})
321321

322-
it('should handle invalid selectors gracefully', () => {
322+
it('should handle non-existent selectors gracefully', () => {
323323
expect(EventHelper.on('#non-existent', 'click', () => {})).toBeNull()
324324
})
325325

0 commit comments

Comments
 (0)