Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
@zerodevx/svelte-toast
Advanced tools
Simple elegant toast notifications.
A feather-light and well-tested toast notification component for modern web frontends in very little lines of code. Compiled (into UMD), it's only 20kB minified (8kB gzipped) and can be used in Vanilla JS, or as a Svelte component.
Because a demo helps better than a thousand API docs: https://zerodevx.github.io/svelte-toast/
Install the package:
$ npm i -D @zerodevx/svelte-toast
The following are exported:
SvelteToast
as the toast container;toast
as the toast controller.If you're using this in a Svelte app, import the toast container and place it in your app shell or root layout.
+layout.svelte
:
<script>
import { SvelteToast } from '@zerodevx/svelte-toast'
// Optionally set default options here
const options = {
...
}
</script>
...
<SvelteToast {options} />
Use anywhere in your app - just import the toast controller.
MyComponent.svelte
:
<script>
import { toast } from '@zerodevx/svelte-toast'
</script>
<button on:click={() => toast.push('Hello world!')}>SHOW TOAST</button>
For any other application with a bundler, something like this should work:
// Import the compiled code from `/dist`
import { SvelteToast, toast } from '@zerodevx/svelte-toast/dist'
const app = new SvelteToast({
// Set where the toast container should be appended into
target: document.body,
props: {
options: {
// Optionally set default options here
...
}
}
})
toast.push('Hello world!')
Or if you prefer to go old-school javascript and a CDN:
<head>
...
<!-- Load `toast` and `SvelteToast` into global scope -->
<script src="https://cdn.jsdelivr.net/npm/@zerodevx/svelte-toast@0"></script>
<!-- Register the app -->
<script>
const toastApp = new SvelteToast({
// Set where the toast container should be appended into
target: document.body,
props: {
options: {
// Optionally set default options here
...
}
}
})
// Now you can `toast` anywhere!
toast.push('Hello world!')
</script>
</head>
In general, use CSS variables - the following (self-explanatory) vars are exposed:
/**
* ._toastContainer
* ┌───────────────────────────────────────┐
* │ ._toastItem │
* │ ┌───────────────────────────────────┐ │
* │ │ ._toastMsg ._toastBtn│ │
* │ │ ┌───────────────────────┐ ┌─────┐ │ │
* │ │ │ │ │ ✕ │ │ │
* │ │ └───────────────────────┘ └─────┘ │ │
* │ │ ._toastBar │ │
* │ │ ═════════════════════════════════ │ │
* │ └───────────────────────────────────┘ │
* └───────────────────────────────────────┘
*/
._toastContainer {
top: var(--toastContainerTop, 1.5rem);
right: var(--toastContainerRight, 2rem);
bottom: var(--toastContainerBottom, auto);
left: var(--toastContainerLeft, auto);
z-index: var(--toastContainerZIndex, 9999);
}
._toastItem {
width: var(--toastWidth, 16rem);
height: var(--toastHeight, auto);
min-height: var(--toastMinHeight, 3.5rem);
margin: var(--toastMargin, 0 0 0.5rem 0);
padding: var(--toastPadding, 0);
background: var(--toastBackground, rgba(66, 66, 66, 0.9));
color: var(--toastColor, #fff);
box-shadow: var(
--toastBoxShadow,
0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06)
);
border: var(--toastBorder, none);
border-radius: var(--toastBorderRadius, 0.125rem);
}
._toastMsg {
padding: var(--toastMsgPadding, 0.75rem 0.5rem);
}
._toastBtn {
width: var(--toastBtnWidth, 2rem);
height: var(--toastBtnHeight, 100%);
font: var(--toastBtnFont, 1rem sans-serif);
}
._toastBtn::after {
content: var(--toastBtnContent, '✕');
}
._toastBar {
background: var(--toastBarBackground, rgba(33, 150, 243, 0.75));
top: var(--toastBarTop, auto);
right: var(--toastBarRight, auto);
bottom: var(--toastBarBottom, 0);
left: var(--toastBarLeft, 0);
height: var(--toastBarHeight, 6px);
width: var(--toastBarWidth, 100%);
}
So to apply your custom theme globally, do something like:
<style>
:root {
--toastBackground: #abcdef;
--toastColor: #123456;
--toastHeight: 300px;
...
}
</style>
To apply CSS overrides to a particular Toast Item (on a per-toast basis), emit the toast with options:
toast.push('Yo!', {
theme: {
'--toastBackground': 'cyan',
'--toastColor': 'black',
...
}
})
where theme
is an object containing one or more CSS var key/value pairs.
For convenient composing, you can either push toasts with
user-defined classes (from v0.7
), or create your own common
toast actions by stubbing them out. For example:
my-theme.js
import { toast } from '@zerodevx/svelte-toast'
export const success = m => toast.push(m, {
theme: {
'--toastBackground': 'green',
'--toastColor': 'white',
'--toastBarBackground': 'olive'
}
})
export const warning = m => toast.push(m, { theme: { ... } })
export const failure = m => toast.push(m, { theme: { ... } })
Then simply import these stubs in your consuming component:
import { success, warning, failure } from './my-theme'
// Do something, then
success('It works!')
Toast messages can be in rich HTML too - for example:
// Definitely not spam
toast.push(`<strong>You won the jackpot!</strong><br>
Click <a href="#" target="_blank">here</a> for details! 😛`)
In a Svelte app, the quickest way to apply custom styles is to wrap the toast container then apply styles on the wrapper:
<style>
.wrap {
display: contents;
font-family: Roboto, sans-serif;
font-size: 0.875rem;
/* You can set CSS vars here too */
--toastBackground: pink;
...
}
.wrap :global(strong) {
font-weight: 600;
}
</style>
<div class="wrap">
<SvelteToast />
</div>
In Vanilla JS, simply apply your styles to the ._toastContainer
class:
._toastContainer {
font-family: Roboto, sans-serif;
--toastBackground: yellow;
...
}
v0.4
It's now easy to send toasts to different container targets within your app. For example:
<script>
import { SvelteToast, toast } from '@zerodevx/svelte-toast'
// Sends a toast to default toast container
toast.push('Yo!')
// Sends a toast to "new" toast container
toast.push('Hey!', { target: 'new' })
</script>
<style>
.wrap {
--toastContainerTop: 0.5rem;
--toastContainerRight: 2rem;
--toastContainerBottom: auto;
--toastContainerLeft: 2rem;
--toastWidth: 100%;
font-size: 0.875rem;
...
}
</style>
<!-- Default toast container -->
<SvelteToast />
<!-- Another toast container -->
<div class="wrap">
<!-- Declare container with a unique `target` name -->
<SvelteToast target="new" options={{ duration: 8000, intro: { y: -64 } }} />
</div>
This has been deprecated, but will remain for backward
compatibility until the next major. The recommended way to remove all toasts from a container target
has changed from pop()
now accepts a filter function.v0.9
.
// Remove all toasts from "new" container
toast.pop((i) => i.target !== 'new') // DEPRECATED
// Or remove ALL active toasts from ALL containers
toast.pop(0)
push()
and set()
can also take an object as its first parameter.
let id = toast.push('Yo!', { duration: 2000 })
// ...is semantically equivalent to
id = toast.push({ msg: 'Yo!', duration: 2000 })
toast.set(id, { msg: 'Waddup!' })
// ...is semantically equivalent to
toast.set({ id, msg: 'Waddup!' })
v0.5
Progress bar tweens can now be paused when the mouse cursor (on desktop) is hovered over the toast
item. This behaviour is disabled by default. To enable, set option pausable: true
.
toast.push('Hover me!', { pausable: true })
To support complex UIs or workflows, a Svelte component can be pushed instead of a standard message
if you're using this in a Svelte project. To do so, push options with component
defined:
import MyCustomSvelteComponent from './MyCustomSvelteComponent.svelte'
toast.push({
component: {
src: MyCustomSvelteComponent, // set `src` to the component itself (required)
props: { ... }, // pass in `props` as key/value pairs (optional)
sendIdTo: 'toastId' // forward toast id to `toastId` prop (optional)
},
... // any other toast options
})
v0.6
onpop()
Callback FunctionA callback function can be run when a toast is closed. To do so, pass the function to the onpop
toast option:
toast.push('Hello world', {
onpop: (id) => { // optionally get the toast id if needed
console.log(`${id} removed`)
},
... // any other toast options
})
v0.7
Custom class names can now be passed into each toast item. Very useful for composing toast actions, or implementing CSS-only dark modes.
<script>
toast.push('Foo', { classes: ['info'] }) // background green
toast.push('Bar', { classes: ['warn'] }) // background red
</script>
<SvelteToast options={{ classes: ['log'] }} />
<style>
:global(.log.info) {
--toastBackground: green;
}
:global(.log.warn) {
--toastBackground: red;
}
</style>
v0.8
This feature uses the Page Visibility API (if it's supported) to pause/resume a toast whenever the browser tab visibility changes - allowing one to emit notifications in the background without it being dismissed prematurely. This now happens automatically and is default behaviour, since notifications should by nature ensure that they're seen.
Additional CSS vars are exposed - specifically, --toastBtnContent
allows the '✕' default character
to be changed. As with CSS content
keys for pseudo elements, url()
can be used to load external
or inline icons.
<script>
import { toast, SvelteToast } from '@zerodevx/svelte-toast'
const options = {
theme: {
'--toastBtnContent': `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' ...")`
}
}
</script>
<button on:click={() => toast.push('Hello!')}>SHOW TOAST</button>
<SvelteToast {options} />
v0.9
Removing all toasts from a particular container target has just become more targeted. Simply call
pop()
with param { target: 'containerName' }
, or call pop(0)
to clear everything.
// Remove all toasts from "new" container
toast.pop({ target: 'new' })
// Or remove ALL active toasts from ALL containers
toast.pop(0)
Under the hood, the project's been migrated to
svelte-package
. For Svelte
consumers, this helps keep in sync with breaking changes that happen in Svelte-world. For other
consumers, there's a change in import path:
// For non-Svelte users, import the compiled code
import { toast, SvelteToast } from '@zerodevx/svelte-toast/dist' // ESM
const { toast, SvelteToast } = require('@zerodevx/svelte-toast/dist') // CJS
// Default options
const options = {
duration: 4000, // duration of progress bar tween to the `next` value
initial: 1, // initial progress bar value
next: 0, // next progress value
pausable: false, // pause progress bar tween on mouse hover
dismissable: true, // allow dismiss with close button
reversed: false, // insert new toast to bottom of stack
intro: { x: 256 }, // toast intro fly animation settings
theme: {}, // css var overrides
classes: [] // user-defined classes
}
/**
* Send a new toast
* @param {(string|SvelteToastOptions)} msg
* @param {SvelteToastOptions} [opts]
* @returns {number}
*/
function push(msg, opts) { ... }
/**
* Remove toast(s)
* @param {(number|Object<'target',string>)} [id]
*/
function pop(id) { ... }
/**
* Update an existing toast
* @param {(number|SvelteToastOptions)} id
* @param {SvelteToastOptions} [opts]
*/
function set(id, opts) { ... }
Packaging is through SvelteKit. Standard Github contribution workflow applies.
Testing is through Playwright. To run the tests headlessly:
$ npm run test
Please refer to the releases page.
ISC
FAQs
Simple elegant toast notifications
We found that @zerodevx/svelte-toast demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.