Widget events
Every widget reports through DOM events rather than callbacks, so a host reacts without polling and without holding a reference to each element.
All of them bubble and are composed, which means they cross the shadow boundary. One listener high up sees everything:
document.addEventListener('selfstore-connected', (e) => {
console.log(e.detail.outcome);
});
Connecting
| Event | Element | Detail |
|---|---|---|
selfstore-connected |
connect, gate | { outcome } |
selfstore-error |
connect | { error } |
selfstore-cancelled |
connect, join | none |
selfstore-gate-deferred |
gate | none |
The gate does not re-emit the connect events - the connect element it builds lives inside it, and the events bubble through on their own.
Status
| Event | Element | Detail |
|---|---|---|
selfstore-status-action |
status | { action } |
action is choose-destination, download, reconnect, unlock or null.
The widget reports which remedy the user asked for; performing it is your app’s
job.
Backups
| Event | Element | Detail |
|---|---|---|
selfstore-backups-opened |
backups | { fileId } |
selfstore-backups-created |
backups | { label } |
selfstore-backups-renamed |
backups | { fileId, label } |
selfstore-backups-deleted |
backups | { fileId } |
selfstore-backups-forgotten |
backups | { fileId } |
selfstore-backups-wrong-password |
backups | { fileId } |
selfstore-backups-error |
backups | { code } |
selfstore-backups-leave |
backups | { fileId } |
selfstore-backups-encrypt |
backups | { fileId, label, active } |
selfstore-backups-share |
backups | { fileId, label, active } |
The last three are requests, not reports. The panel surfaces an intent it cannot carry out on its own - leaving a share, encrypting a backup, opening a share panel - and your app owns what happens next. Everything above them describes something that already happened.
Sharing
| Event | Element | Detail |
|---|---|---|
selfstore-link-created |
share | { link } |
selfstore-link-copied |
share | { url } |
selfstore-share-stopped |
share | none |
Joining
| Event | Element | Detail |
|---|---|---|
selfstore-joined |
join | { outcome: 'joined' } |
selfstore-join-refused |
join | { outcome } |
selfstore-join-refused is not a failure channel. It names the two outcomes the
journey expects - a spent invitation, or a device already following another
share - so your app offers the right next step instead of a retry that will fail
the same way.
Listening in a framework
React before 19 does not map custom events to on* props, so use a ref and
addEventListener. Vue’s @selfstore-connected and Svelte’s
on:selfstore-connected work directly. See using the widgets in a
framework.
Errors have codes too
These events report what the user did or saw. The store’s own failures are a separate, typed surface - see error codes.