# Custom events and slots

Source: https://signy.dev/documentation/1/components/events-and-slots/

Talk to the page around a Shadow DOM component.

A Shadow DOM component mounted into an element of a page, such as an embeddable widget, cannot take props from that page. It talks back with custom events, and the page passes content in through slots.

## emit()

`this.emit(name, detail)` dispatches a custom event from the component's host element. It bubbles and is `composed`, so it leaves the shadow root and the page can listen to it like any event.

```js
// In the component
this.emit('rate', { stars: 4 });

// On the page
host.addEventListener('rate', (event) => console.log(event.detail.stars));
```

Between Signy components without Shadow DOM, pass a callback prop instead, such as `onChange=${...}`.

## Slots

Children of the host element in the page show where the component renders a `<slot>`. A child with `slot="name"` goes to `<slot name="name">`. `getSlottedElements(name)` lists them, `hasSlottedContent(name)` checks for any, and `onSlotChange(name, callback)` follows changes. Call `onSlotChange()` in `setup()`, it is removed on unmount.

```js
const { Component, Signal, application, html } = Signy;

class Rating extends Component {
  shadow = true;
  styles = `
    button { font-size: 1.5rem; border: 0; background: none; cursor: pointer; }
    ::slotted(*) { display: block; font-weight: 700; }
  `;
  stars = new Signal(0);

  rate(stars) {
    this.stars.value = stars;
    this.emit("rate", { stars });
  }

  render() {
    return html`
      <div>
        <slot name="label"></slot>
        ${[1, 2, 3, 4, 5].map((n) => html`
          <button aria-label=${`${n} stars`} onClick=${() => this.rate(n)}>
            ${() => (n <= this.stars.value ? "★" : "☆")}
          </button>
        `)}
      </div>
    `;
  }
}

// The host's own children fill the slots
const host = document.querySelector("#events-and-slots");
host.innerHTML = '<span slot="label">How was your coffee?</span>';
const output = document.createElement("output");
host.after(output);
host.addEventListener("rate", (event) => (output.textContent = `The page heard ${event.detail.stars} stars.`));

application(Rating).mount(host);
```
