Get started
The form (Svelte)
The same state machine, as a readable store: $form for the values, the
methods on form for everything the reporter does.
svelte
<script lang="ts">
import { createBugReport } from "bugbottle/svelte";
import { htmlToImage } from "bugbottle/html-to-image"; // optional
import { onDestroy, onMount } from "svelte";
const form = createBugReport({
endpoint: "/api/feedback",
screenshot: htmlToImage, // leave out to disable screenshots
});
onMount(() => form.open());
onDestroy(() => form.destroy());
</script>
<form data-bugbottle on:submit|preventDefault={() => form.submit()}>
{#each $form.types as t}
<button type="button" on:click={() => form.setType(t)}>{t}</button>
{/each}
<textarea value={$form.message} on:input={(e) => form.setMessage(e.currentTarget.value)} />
{#if $form.canScreenshot}
<label>
<input
type="checkbox"
checked={$form.includeScreenshot}
on:change={(e) => form.toggleScreenshot(e.currentTarget.checked)}
/>
Attach a picture of this page
</label>
{/if}
{#if $form.screenshot}<img src={$form.screenshot} alt="" />{/if}
<button type="button" on:click={() => form.pickElement()}>
{$form.isPicking ? "Click anything to attach it — Esc to stop" : "Point at the element"}
</button>
<ul>
{#each $form.elements as el, i}
<li><code>{el.selector}</code> {el.text}
<button type="button" on:click={() => form.removeElement(i)}>×</button>
</li>
{/each}
</ul>
<p role="status">{$form.statusMessage}</p>
<button disabled={$form.isSending}>Send</button>
</form>svelte is an optional peer dependency, and only its Readable type is used:
the store contract is one function, implemented here, so the adapter adds no
runtime dependency at all.