@onEvent

The @onEvent decorator provides a declarative way to subscribe to DOM events on elements within or outside your host. It automatically handles event subscription and unsubscription, ensuring no memory leaks when your host disconnects.

Usage

import { RadiantElement, customElement, onEvent } from '@ecopages/radiant';
 
@customElement('click-counter')
export class ClickCounter extends RadiantElement {
	private count = 0;
	
	@onEvent({ selector: 'button', type: 'click' })
	handleClick(event: MouseEvent) {
		this.count++;
		console.log(`Clicked ${this.count} times`);
	}
}

Parameters

The decorator accepts a configuration object with exactly one target and an event type. A config with two targets, such as host and selector, is a type error.

Target Selection (Choose one)

ParameterTypeDescription
selectorstringCSS selector to match target elements (uses event delegation).
refstringValue of data-ref attribute to match (uses event delegation).
hosttrueListen on the Element Host itself, so non-bubbling events such as pointerenter and pointerleave work.
windowtrueListen on the global window object.
documenttrueListen on the global document object.
mediaQuerystringListen on the MediaQueryList of the query, with type: 'change'.

Event Configuration

ParameterTypeRequiredDescription
typestringYesEvent type (e.g., 'click', 'input', 'keydown').
optionsAddEventListenerOptionsNoStandard options like { passive: true, once: true }.

Common Use Cases

Using data-ref

This is the recommended approach for referencing elements within your component's structure.

@onEvent({ ref: 'submit-btn', type: 'click' })
handleSubmit(event: MouseEvent) {
	event.preventDefault();
	console.log('Form submitted');
}

HTML:

<form-component>
	<button data-ref="submit-btn" type="submit">Submit</button>
</form-component>

Window and Document Events

@onEvent({ window: true, type: 'scroll', options: { passive: true } })
handleScroll() {
	console.log(`Scrolled to: ${window.scrollY}px`);
}
 
@onEvent({ document: true, type: 'keydown' })
handleKeyDown(event: KeyboardEvent) {
	if (event.key === 'Escape') this.close();
}

Host Events

host: true attaches directly to the Element Host with the same connect and cleanup lifecycle as window and document, and does nothing on the server. On a RadiantController it listens on the element the controller is attached to. It is the supported form for non-bubbling events on the host.

@onEvent({ host: true, type: 'pointerenter' })
handlePointerEnter() {
	this.open = true;
}
 
@onEvent({ host: true, type: 'pointerleave' })
handlePointerLeave() {
	this.open = false;
}

RadiantController usage

@onEvent(...) also works on RadiantController.

Use it when the controller is enhancing authored HTML, delegating across DOM it does not own through render(), or listening on window / document.

If the controller owns the markup through render(), prefer direct JSX handlers such as on:click for local interactions.

import { RadiantController, onEvent, state } from '@ecopages/radiant';
 
export class ClickCounterController extends RadiantController {
	@state count = 0;
 
	@onEvent({ ref: 'button', type: 'click' })
	handleClick() {
		this.count += 1;
	}
}

HTML:

<section data-controller="click-counter">
	<button data-ref="button">Clicked 0 times</button>
</section>

Event Delegation for Non-Bubbling Events

Since selector and ref use event delegation, they rely on event bubbling. For events that don't bubble (like focus), use their bubbling alternatives (like focusin), pass options: { capture: true }, or use host: true when the event fires on the host itself. When the app's bundler sets process.env.NODE_ENV to anything other than "production", Radiant warns once per event type when a delegated listener uses a known non-bubbling type. A production build, and a page loaded without a bundler, log nothing. To silence Radiant and JSX development warnings together, call setDevWarningsEnabled(false) from @ecopages/jsx/jsx-dev-runtime; true or undefined enables them again.

// Works: focusin bubbles
@onEvent({ selector: 'input', type: 'focusin' })
handleFocus() { /* ... */ }

Learn More

  • Best Practices - Learn more about component design and events.
  • @query - Query elements to use with events.
  • @debounce - Debounce frequent event handlers.