# Widget events Source: https://selfstore.dev/docs/events Every selfstore-* DOM event the widgets emit, with the shape of its detail and which element fires it. All of them bubble and are composed, so one listener on an ancestor sees the lot. 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: ```ts 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](https://selfstore.dev/docs/widgets-frameworks). ## 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](https://selfstore.dev/docs/errors). Map of this site for a model: https://selfstore.dev/llms.txt Every page in one file: https://selfstore.dev/llms-full.txt