Radiant0.3.0-rc.2

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:

ParameterTypeRequiredDescription
hostRadiantElementYesThe host element for lifecycle binding.
configOnEventConfigYesEvent configuration (see below).
callback(event: Event) => voidYesThe event handler.

OnEventConfig — Target Selection (choose one)

FieldTypeDescription
selectorstringCSS selector for delegated matching.
refstringValue of data-ref for delegated matching.
windowtrueListen on the global window object.
documenttrueListen on the global document object.

OnEventConfig — Event Options

FieldTypeRequiredDescription
typestringYesEvent type (e.g., 'click', 'input', 'keydown').
optionsAddEventListenerOptionsNoStandard options like { passive: true, once: true }.
scope'light' | 'shadow' | 'both'NoWhich 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'),
);
ScopeBehavior
'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