Events
A component needs a way to tell the rest of the page what just happened: a value changed, a panel closed, an option got picked. Eunomia does this the same way across every component, so once one event makes sense, every other component's events do too.
The naming convention
Almost every event a component dispatches is named eun, followed by
what happened, in one word: eunchange, eunclose, eunselect,
euncommit, eunconfirm. Seeing a eun-prefixed event name in an
inspector or a console log immediately tells you it came from a Eunomia
component, not from the browser itself or from another library sharing
the page.
document.querySelector("eun-select").addEventListener("eunchange", (event) => {
console.log(event.target.value);
});
One exception steps outside that convention on purpose, reusing a
native browser event name instead of inventing a eun-prefixed one:
Button and Fab dispatch the platform's own command event on their
target as part of the
Reading what happened
An event rarely needs to say more than what already happened, so most of the useful information lives in one of two places instead of a generic grab-bag object.
For anything describing the component's own current state, read it
straight off event.target, the same element already sitting in your
markup:
select.addEventListener("eunchange", (event) => {
console.log(event.target.value);
});
For anything specific to that one occurrence and not part of the component's ongoing state, such as which option was picked from a menu or which slide is now active in a carousel, it's exposed as a plain, read-only property directly on the event object itself:
dropdownButton.addEventListener("eunselect", (event) => {
console.log(event.key);
});
carousel.addEventListener("eunslidechange", (event) => {
console.log(event.index, event.count);
});
Either way, nothing is ever buried inside a generic event.detail
object: every event exposes a small, specific, named shape instead, and
every one of those shapes is fully typed if the project uses
TypeScript, letting an editor autocomplete event.target.value or
event.key rather than requiring a lookup on this page every time. See
Bubbling
Every Eunomia event bubbles, so a single listener on a shared ancestor,
such as a <form> or a whole page section, catches every matching
event from any component inside it, without wiring a listener onto each
one by hand:
document.querySelector("form").addEventListener("eunchange", (event) => {
console.log(event.target.name, event.target.value);
});
Whether a given event also crosses a shadow DOM boundary (composed)
depends on that specific event: one meant to stay inside the component
that raised it stops there, while one meant to be caught from anywhere
on the page, even through another component's shadow root, is composed
so it can. Neither detail changes how you listen for it: it only
matters if a listener sits on document itself, above every shadow
root, rather than somewhere inside the same component tree.
Every event
Every custom event the library dispatches, what raises it, and what it
means. Each component's own API tab still has the last word on the
exact typed shape, for example
| Event | Dispatched by | Means |
|---|---|---|
eunchange |
Any form field ( |
The component's own current value or state changed |
euncommit |
Any editable form field ( |
The current value is finalized (e.g. on blur), separate from every keystroke's own eunchange |
eunclose |
The close action was activated, or (Toast) its exit transition finished | |
eunselect |
An option was chosen from the menu | |
eunconfirm |
The confirm action was triggered. Cancelable | |
euncancel |
The cancel action was triggered, including an implicit backdrop click or Escape | |
eundirtyclose |
A close attempt was gated behind unsaved changes, right before the discard-confirmation modal opens. Cancelable | |
eunnavigate |
href set), |
About to follow a link; lets an SPA router intercept it. Cancelable |
eunloadmore |
Scrolled near the end of the known items; fetch the next page | |
eunpagechange |
A page control was activated, requesting a page change | |
eunpagesizechange |
A new page size was chosen | |
eunstepchange |
A reachable step was activated, requesting navigation to it | |
tabchange |
The active tab changed | |
eunslidechange |
The active slide settled on a new index | |
eunimagechange |
The active image changed | |
eunfilereject |
One or more offered files were rejected (type, size, or count) | |
eunslotclick |
An empty hour-grid slot was clicked, a create-event intent | |
euneventclick |
An event chip was clicked | |
euneventcreate |
A new event was saved | |
euneventupdate |
An existing event was saved, or moved by dragging it to a new time or day | |
euneventdelete |
An event was deleted | |
eunguestinvite |
A guest email was added to an event's guest list | |
eunperiodchange |
The visible date range changed | |
euntableaction |
An action button, or a dropdown action's option, was activated | |
euntablerowclick |
A row was clicked outside its checkbox or action cell | |
euntableselectionchange |
The set of selected rows changed | |
command |
An invoker requested a command on its target; see |
Two names above don't carry the eun prefix: command is the deliberate platform-native reuse covered under The naming convention above, while tabchange is a naming exception with no further significance, listen for it exactly as written.
See also
Types : importing an event's exact TypeScript shape for a fully-typed listenerInvoker Commands API : the nativecommandevent Button, Fab, Modal, and Drawer shareForms & validation : reading a submitted form's values without listening to every field's own change event