diff --git a/packages/core/src/events.ts b/packages/core/src/events.ts index 4e6dc4b..29e02d9 100644 --- a/packages/core/src/events.ts +++ b/packages/core/src/events.ts @@ -10,75 +10,53 @@ import { connected } from './lifecycle'; * * @param eventName - The name of the event to listen for (e.g., 'click', 'focus', 'custom-event') * @param selector - CSS selector to match elements against - * @param callback - Function to invoke when the event occurs. Receives the event and matching element. + * @param callback - Function to invoke when the event occurs. Receives the event. * @param options - Optional event listener options (capture, once, passive, etc.) * @returns A cleanup function that removes all event listeners and stops observing * * @example * ```ts * // Listen for clicks on all buttons with inferred types - * on('click', 'button', (event, element) => { - * console.log('Button clicked:', element); + * on('click', 'button', (event) => { + * console.log('Button clicked:', event.currentTarget); * }); * * // Use event listener options - * on('click', '.once-button', (event, element) => { + * on('click', '.once-button', (event) => { * console.log('Clicked once'); * }, { once: true }); * * // Manual cleanup - * const stop = on('click', 'button', (event, element) => { + * const stop = on('click', 'button', (event) => { * console.log('Clicked'); * }); * stop(); * ``` */ // Tag name selector with inferred event type: on('click', 'button', ...) -export function on( +export function on( eventName: E, - selector: K, - callback: (event: HTMLElementEventMap[E], element: HTMLElementTagNameMap[K]) => void, - options?: boolean | AddEventListenerOptions -): () => void; -// Tag name selector with custom event type: on('ajax', 'form', ...) -export function on( - eventName: string, - selector: K, - callback: (event: Ev, element: HTMLElementTagNameMap[K]) => void, - options?: boolean | AddEventListenerOptions -): () => void; -// Element type with native event: on('click', '.btn', ...) - event inferred from eventName -export function on( - eventName: keyof HTMLElementEventMap, selector: string, - callback: (event: Event, element: T) => void, + callback: (event: HTMLElementEventMap[E]) => void, options?: boolean | AddEventListenerOptions ): () => void; -// Custom selector with native event and element type: on<'click', HTMLButtonElement>('click', '.btn', ...) -export function on( - eventName: E, - selector: string, - callback: (event: HTMLElementEventMap[E], element: T) => void, - options?: boolean | AddEventListenerOptions -): () => void; -// Custom selector with custom event type: on('ajax', '.form', ...) -export function on( +// Tag name selector with custom event type: on('ajax', 'form', ...) +export function on( eventName: string, selector: string, - callback: (event: Ev, element: T) => void, + callback: (event: Ev) => void, options?: boolean | AddEventListenerOptions ): () => void; export function on( eventName: string, selector: string, - callback: (event: Event, element: Element) => void, + callback: (event: Event) => void, options?: boolean | AddEventListenerOptions ): () => void { return connected(selector, (element) => { - const handler = (event: Event) => callback(event, element); - element.addEventListener(eventName, handler, options); + element.addEventListener(eventName, callback, options); return () => { - element.removeEventListener(eventName, handler, options); + element.removeEventListener(eventName, callback, options); }; }); } diff --git a/packages/core/test/events.test.ts b/packages/core/test/events.test.ts index f82dfc7..3a9b4fd 100644 --- a/packages/core/test/events.test.ts +++ b/packages/core/test/events.test.ts @@ -11,7 +11,6 @@ describe('on', () => { root.click(); expect(callback.calledOnce).to.be.true; expect(callback.firstCall.args[0]).to.be.instanceOf(Event); - expect(callback.firstCall.args[1]).to.eq(root); }); it('cleans up the event listener manually', async () => { @@ -51,6 +50,15 @@ describe('on', () => { root.click(); expect(callback.callCount).to.eq(1); }); + + it('supports custom events', async () => { + const callback = Sinon.spy(); + const root = await fixture(html``); + on>('ajax:success', '#selector', callback); + + emit<{ foo: string }>(root, 'ajax:success', { detail: { foo: 'bar' } }); + expect(callback.calledOnce).to.be.true; + }); }); describe('emit', () => { diff --git a/packages/docs/utilities/on.md b/packages/docs/utilities/on.md index 02091d9..cf5ce1e 100644 --- a/packages/docs/utilities/on.md +++ b/packages/docs/utilities/on.md @@ -4,34 +4,37 @@ The `on` function sets up event listeners that automatically attach to elements ## Usage -This function observes the DOM and automatically adds event listeners to elements matching the provided CSS selector. Event listeners are added to existing elements immediately and to new elements as they're added to the DOM. When elements are removed or no longer match the selector, their event listeners are automatically cleaned up. +This function observes the DOM and automatically adds event listeners to elements matching the provided CSS selector. +Event listeners are added to existing elements immediately and to new elements as they're added to the DOM. When +elements are removed or no longer match the selector, their event listeners are automatically cleaned up. ```ts import { on } from '@ambiki/impulse'; // Listen for clicks on all buttons -on('click', 'button', (event, element) => { - console.log('Button clicked: ', element); +on('click', 'button', (event) => { + console.log('Button clicked: ', event.currentTarget); }); ``` ## Event listener options -You can pass standard event listener [options](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options) to customize the behavior: +You can pass standard event listener [options](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options) +to customize the behavior: ```ts{4,9,14} // Fire the event listener only once -on('click', '.once-button', (event, element) => { +on('click', '.once-button', (event) => { console.log('Clicked once'); }, { once: true }); // Use capture phase -on('focus', 'input', (event, element) => { +on('focus', 'input', (event) => { console.log('Input focused'); }, { capture: true }); // Mark as passive for better scroll performance -on('touchstart', '.slider', (event, element) => { +on('touchstart', '.slider', (event) => { handleTouch(event); }, { passive: true }); ``` @@ -41,7 +44,7 @@ on('touchstart', '.slider', (event, element) => { The `on` function returns a cleanup function that removes all event listeners and stops observing when called. ```ts{1,6} -const stop = on('click', 'button', (event, element) => { +const stop = on('click', 'button', (event) => { console.log('Clicked'); });