sonner-wc 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ben Nyberg
4
+
5
+ This project, `sonner-wc`, is a port of Sonner — a React toast library by
6
+ Emil Kowalski (https://github.com/emilkowalski/sonner) — to a framework-agnostic
7
+ web component. Substantial portions of the CSS, the icon SVGs, the constants,
8
+ and the swipe and stacking algorithms are derived directly from Sonner.
9
+
10
+ Copyright (c) 2023 Emil Kowalski
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # sonner-wc
2
+
3
+ [Sonner](https://github.com/emilkowalski/sonner)'s toast UX as a framework-agnostic web component.
4
+ Drop it into anything — vanilla HTML, Vue, Svelte, htmx — same API, same look.
5
+
6
+ ```html
7
+ <script type="module" src="https://unpkg.com/sonner-wc/dist/sonner-wc.bundle.js"></script>
8
+
9
+ <sonner-toaster position="bottom-right" theme="system" rich-colors></sonner-toaster>
10
+
11
+ <script type="module">
12
+ import { toast } from 'sonner-wc';
13
+ toast.success('Saved!', { description: 'Your changes are live.' });
14
+ </script>
15
+ ```
16
+
17
+ ## Install
18
+
19
+ ```sh
20
+ bun add sonner-wc # or npm / pnpm / yarn
21
+ ```
22
+
23
+ ESM only. Modern browsers (last two versions of Chrome, Firefox, Safari, Edge).
24
+
25
+ ## API
26
+
27
+ ### The `<sonner-toaster>` host
28
+
29
+ Place once, anywhere in the page. Configure with attributes.
30
+
31
+ | Attribute | Values | Default |
32
+ | ---------------- | -------------------------------------------------------------------------------- | -------------- |
33
+ | `position` | `top-left` `top-center` `top-right` `bottom-left` `bottom-center` `bottom-right` | `bottom-right` |
34
+ | `theme` | `light` `dark` `system` | `light` |
35
+ | `rich-colors` | presence flag | off |
36
+ | `close-button` | presence flag (applies to all toasts) | off |
37
+ | `invert` | presence flag | off |
38
+ | `dir` | `ltr` `rtl` `auto` | `auto` |
39
+ | `gap` | number (px) between stacked toasts | `14` |
40
+ | `duration` | default lifetime in ms (per-toast override wins) | `4000` |
41
+ | `visible-toasts` | how many toasts fan out vs. stack behind | `3` |
42
+ | `offset` | viewport gutter; px number or CSS length | `24px` |
43
+ | `mobile-offset` | gutter under 600px | `16px` |
44
+ | `hotkey` | `Alt+KeyT`-style chord that expands the stack | `altKey+KeyT` |
45
+
46
+ ### The `toast()` helper
47
+
48
+ ```ts
49
+ import { toast } from 'sonner-wc';
50
+
51
+ toast('Default message');
52
+ toast.success('Saved', { description: 'Looks good.' });
53
+ toast.error('Oops', { action: { label: 'Retry', onClick: () => retry() } });
54
+ toast.warning('Storage almost full');
55
+ toast.info('You have a new message');
56
+ toast.loading('Uploading…', { id: 'job-1' });
57
+
58
+ // Update in place by reusing an id:
59
+ toast.success('Done', { id: 'job-1' });
60
+
61
+ // Async with automatic loading → success/error:
62
+ toast.promise(api.save(), {
63
+ loading: 'Saving…',
64
+ success: (data) => `Saved as ${data.name}`,
65
+ error: (err) => `Couldn't save: ${err.message}`,
66
+ });
67
+
68
+ // Anything custom:
69
+ toast.custom((id) => {
70
+ const el = document.createElement('div');
71
+ el.innerHTML = `<strong>Custom</strong> content #${id}`;
72
+ return el;
73
+ });
74
+
75
+ // Dismiss programmatically:
76
+ toast.dismiss('job-1'); // by id
77
+ toast.dismiss(); // all
78
+ ```
79
+
80
+ The returned element is the `<sonner-toast>` itself. You can mutate it directly — set
81
+ attributes, add children, call `.dismiss()`, etc.
82
+
83
+ ### Declarative form
84
+
85
+ You can also build toasts as plain HTML and append them to a toaster:
86
+
87
+ ```html
88
+ <sonner-toaster id="t" position="bottom-right"></sonner-toaster>
89
+
90
+ <script>
91
+ const el = document.createElement('sonner-toast');
92
+ el.setAttribute('type', 'success');
93
+ el.setAttribute('duration', '4000');
94
+ el.innerHTML = `
95
+ <span slot="title">Saved!</span>
96
+ <span slot="description">Your changes are live.</span>
97
+ <button slot="action">View</button>
98
+ `;
99
+ document.getElementById('t').appendChild(el);
100
+ </script>
101
+ ```
102
+
103
+ ### Slots on `<sonner-toast>`
104
+
105
+ | Slot | Purpose |
106
+ | ------------- | -------------------------------------------- |
107
+ | `title` | Primary text |
108
+ | `description` | Secondary text |
109
+ | `icon` | Icon override (otherwise picked from `type`) |
110
+ | `action` | Right-aligned action button |
111
+ | `cancel` | Left-aligned cancel button |
112
+
113
+ ### Theming
114
+
115
+ All colors are CSS custom properties on the toaster. Override them in your own
116
+ CSS to restyle:
117
+
118
+ ```css
119
+ sonner-toaster {
120
+ --normal-bg: #1a1a1a;
121
+ --normal-border: #2a2a2a;
122
+ --normal-text: #fafafa;
123
+ --success-bg: #042f2e;
124
+ --success-text: #5eead4;
125
+ --border-radius: 12px;
126
+ --width: 400px;
127
+ }
128
+ ```
129
+
130
+ ## Development
131
+
132
+ ```sh
133
+ bun install
134
+ bun run dev # builds with watch + serves demo at http://localhost:3000
135
+ bun test # runs unit tests
136
+ bun run build # produces dist/
137
+ ```
138
+
139
+ ## Credits
140
+
141
+ UI behavior and visual design are direct ports of [Sonner](https://github.com/emilkowalski/sonner) by Emil Kowalski.
142
+ This package adapts that work into a framework-agnostic web component.
143
+
144
+ MIT.
@@ -0,0 +1,10 @@
1
+ export declare const VISIBLE_TOASTS_AMOUNT = 3;
2
+ export declare const VIEWPORT_OFFSET = "24px";
3
+ export declare const MOBILE_VIEWPORT_OFFSET = "16px";
4
+ export declare const TOAST_LIFETIME = 4000;
5
+ export declare const TOAST_WIDTH = 356;
6
+ export declare const GAP = 14;
7
+ export declare const SWIPE_THRESHOLD = 45;
8
+ export declare const SWIPE_VELOCITY_THRESHOLD = 0.11;
9
+ export declare const TIME_BEFORE_UNMOUNT = 200;
10
+ export declare const DEFAULT_HOTKEY: readonly string[];
@@ -0,0 +1,8 @@
1
+ import type { ToastType } from './types.js';
2
+ export declare const SUCCESS_ICON = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\" fill=\"currentColor\" height=\"20\" width=\"20\"><path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z\" clip-rule=\"evenodd\"/></svg>";
3
+ export declare const WARNING_ICON = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\" height=\"20\" width=\"20\"><path fill-rule=\"evenodd\" d=\"M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z\" clip-rule=\"evenodd\"/></svg>";
4
+ export declare const INFO_ICON = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\" fill=\"currentColor\" height=\"20\" width=\"20\"><path fill-rule=\"evenodd\" d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z\" clip-rule=\"evenodd\"/></svg>";
5
+ export declare const ERROR_ICON = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 20 20\" fill=\"currentColor\" height=\"20\" width=\"20\"><path fill-rule=\"evenodd\" d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z\" clip-rule=\"evenodd\"/></svg>";
6
+ export declare const CLOSE_ICON = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line></svg>";
7
+ export declare const LOADING_SPINNER: string;
8
+ export declare function getTypeIcon(type: ToastType): string | null;
@@ -0,0 +1,12 @@
1
+ export { SonnerToast } from './sonner-toast.js';
2
+ export { SonnerToaster } from './sonner-toaster.js';
3
+ export { toast } from './toast.js';
4
+ import type { SonnerToast as _SonnerToast } from './sonner-toast.js';
5
+ import type { SonnerToaster as _SonnerToaster } from './sonner-toaster.js';
6
+ export type { Position, PromiseOptions, SonnerToastElement, SonnerToasterElement, SwipeDirection, Theme, ToastAction, ToastContent, ToastOptions, ToastType, ToasterOptions, } from './types.js';
7
+ declare global {
8
+ interface HTMLElementTagNameMap {
9
+ 'sonner-toast': _SonnerToast;
10
+ 'sonner-toaster': _SonnerToaster;
11
+ }
12
+ }
@@ -0,0 +1,11 @@
1
+ import type { SonnerToastElement, SonnerToasterElement } from './types.js';
2
+ export declare function nextToastId(): number;
3
+ export declare function registerToast(el: SonnerToastElement): void;
4
+ export declare function unregisterToast(el: SonnerToastElement): void;
5
+ export declare function getToast(id: string | number): SonnerToastElement | undefined;
6
+ export declare function registerToaster(el: SonnerToasterElement): void;
7
+ export declare function unregisterToaster(el: SonnerToasterElement): void;
8
+ /** Find a toaster element to host a new toast. Prefer the most-recently-connected one. */
9
+ export declare function defaultToaster(): SonnerToasterElement | undefined;
10
+ export declare function allToasters(): readonly SonnerToasterElement[];
11
+ export declare function dismissAllToasts(): void;
@@ -0,0 +1,29 @@
1
+ import type { SonnerToastElement, ToastContent, ToastOptions, ToastType } from './types.js';
2
+ declare const HTMLElementCtor: typeof HTMLElement;
3
+ export declare class SonnerToast extends HTMLElementCtor implements SonnerToastElement {
4
+ #private;
5
+ static get observedAttributes(): string[];
6
+ toastId: string | number;
7
+ constructor();
8
+ connectedCallback(): void;
9
+ disconnectedCallback(): void;
10
+ attributeChangedCallback(name: string, _old: string | null, _val: string | null): void;
11
+ get toastType(): ToastType;
12
+ /** Imperative dismiss — triggers exit animation, then removes from DOM. */
13
+ dismiss(): void;
14
+ /** Apply new content/options. Used by toast.promise() for loading→success/error transitions. */
15
+ update(options: ToastOptions & {
16
+ title?: ToastContent;
17
+ type?: ToastType;
18
+ }): void;
19
+ setTitle(value: ToastContent): void;
20
+ setDescription(value: ToastContent | undefined): void;
21
+ setIcon(value: Node | string | null | undefined): void;
22
+ setHandlers(handlers: {
23
+ onDismiss?: ToastOptions['onDismiss'];
24
+ onAutoClose?: ToastOptions['onAutoClose'];
25
+ }): void;
26
+ /** Called by the toaster to pause/resume this toast's auto-dismiss timer. */
27
+ setPaused(paused: boolean): void;
28
+ }
29
+ export {};
@@ -0,0 +1,20 @@
1
+ import type { SonnerToastElement, SonnerToasterElement } from './types.js';
2
+ declare const HTMLElementCtor: typeof HTMLElement;
3
+ export declare class SonnerToaster extends HTMLElementCtor implements SonnerToasterElement {
4
+ #private;
5
+ static get observedAttributes(): string[];
6
+ constructor();
7
+ connectedCallback(): void;
8
+ disconnectedCallback(): void;
9
+ attributeChangedCallback(name: string, _old: string | null, _val: string | null): void;
10
+ addToast(el: SonnerToastElement): SonnerToastElement;
11
+ /** Re-announce `text` through the dedicated assertive live region. Used when
12
+ * a toast transitions to an urgent type (error/warning) after mount — the
13
+ * toast's own aria-live change is unreliable across screen readers, so we
14
+ * route the alert through a fresh region whose announcement behavior is
15
+ * consistent. The clear-then-set pattern forces SRs that diff content to
16
+ * treat the new text as a new announcement. */
17
+ announceUrgent(text: string): void;
18
+ dismissAll(): void;
19
+ }
20
+ export {};