createEventListener
createEventListener subscribes to DOM events with delegation, window, or document targeting. It is the functional equivalent of the @onEvent decorator, designed for vanilla JS usage.
Listeners are automatically attached on connect and detached on disconnect. The returned cleanup function permanently removes the listener.
Usage
import { RadiantElement } from '@ecopages/radiant';
import { createEventListener } from '@ecopages/radiant/helpers/create-event-listener';
class ClickCounter extends RadiantElement {
#count = 0;
constructor() {
super();
createEventListener(this, { selector: 'button', type: 'click' }, () => {
this.#count++;
console.log(`Clicked ${this.#count} times`);
});
}
}
customElements.define('click-counter', ClickCounter);Use it with authored light DOM:
<click-counter>
<button type="button">Click me</button>
</click-counter>Parameters
createEventListener(host, config, callback) accepts:
| Parameter | Type | Required | Description |
|---|---|---|---|
host | RadiantElement | Yes | The host element for lifecycle binding. |
config | OnEventConfig | Yes | Event configuration (see below). |
callback | (event: Event) => void | Yes | The event handler. |
OnEventConfig — Target Selection (choose one)
| Field | Type | Description |
|---|---|---|
selector | string | CSS selector for delegated matching. |
ref | string | Value of data-ref for delegated matching. |
window | true | Listen on the global window object. |
document | true | Listen on the global document object. |
OnEventConfig — Event Options
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Event type (e.g., 'click', 'input', 'keydown'). |
options | AddEventListenerOptions | No | Standard options like { passive: true, once: true }. |
scope | 'light' | 'shadow' | 'both' | No | Which DOM tree to observe for delegated events (default: 'light'). |
Return Value
Returns a () => void cleanup function. Calling it permanently removes the listener, even across reconnects.
const cleanup = createEventListener(host, { ref: 'btn', type: 'click' }, handler);
// Later: permanently stop listening
cleanup();Delegated Events
When using selector or ref, the listener is delegated to the host element. Events bubble up from the target, and the handler fires only when event.target matches the selector.
Because delegation relies on bubbling, it does not work with events that do not bubble (e.g., focus, blur). Use their bubbling alternatives (focusin, focusout).
Shadow DOM Scope
Use scope to control where delegated listeners are attached.
createEventListener(
host,
{ ref: 'shadow-btn', type: 'click', scope: 'shadow' },
() => console.log('clicked inside shadow root'),
);
createEventListener(
host,
{ ref: 'shared-btn', type: 'click', scope: 'both' },
() => console.log('clicked in either tree'),
);| Scope | Behavior |
|---|---|
'light' | Delegates on the host element (default). |
'shadow' | Delegates on the host's shadow root. |
'both' | Delegates on both the host and its shadow root. |
If the shadow root does not exist at registration time but is created later via attachShadow, the listener is attached automatically.
Window and Document Events
createEventListener(
host,
{ window: true, type: 'scroll', options: { passive: true } },
() => console.log('scrolled'),
);
createEventListener(
host,
{ document: true, type: 'keydown' },
(event) => {
if ((event as KeyboardEvent).key === 'Escape') {
console.log('escape pressed');
}
},
);Learn More
@onEvent— Decorator equivalent.createEvent— Dispatch custom events.debounce— Debounce frequent event handlers.