├── .browserslistrc ├── .circleci └── config.yml ├── .eslintrc ├── .gitignore ├── .npmignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build ├── bundle.css ├── bundle.js └── bundle.mjs ├── cypress.config.cjs ├── cypress ├── integration │ └── notifications.spec.js └── support │ ├── commands.js │ └── index.js ├── example ├── App.svelte ├── Children.svelte ├── CustomItem.svelte └── index.js ├── index.d.ts ├── package-lock.json ├── package.json ├── public ├── favicon.png ├── global.css └── index.html ├── rollup.config.js └── src ├── components ├── DefaultNotification.svelte ├── Notification.svelte └── Notifications.svelte ├── context.js ├── index.js ├── positions.js └── store ├── actions ├── addNotification.js ├── clearNotifications.js └── removeNotification.js └── index.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | # Browsers that we support 2 | 3 | last 2 version 4 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | run-checks: 4 | docker: 5 | - image: cypress/base:14.16.0 6 | environment: 7 | TERM: xterm 8 | steps: 9 | - checkout 10 | - run: 11 | name: Install dependencies 12 | command: npm install 13 | - run: 14 | name: Check code with eslint 15 | command: npm run lint 16 | - run: 17 | name: Run end-to-end tests 18 | command: npm run ci 19 | workflows: 20 | version: 2 21 | stack: 22 | jobs: 23 | - run-checks 24 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "parserOptions": { 4 | "ecmaVersion": 2019, 5 | "sourceType": "module" 6 | }, 7 | "env": { 8 | "es6": true, 9 | "browser": true, 10 | "cypress/globals": true 11 | }, 12 | "plugins": ["svelte3", "cypress"], 13 | "overrides": [ 14 | { 15 | "files": ["**/*.svelte"], 16 | "processor": "svelte3/svelte3", 17 | "rules": { 18 | "prefer-const": "off", 19 | "import/first": "off", 20 | "import/no-duplicates": "off", 21 | "import/no-mutable-exports": "off", 22 | "import/no-unresolved": "off", 23 | "import/prefer-default-export": "off" 24 | } 25 | } 26 | ], 27 | "rules": { 28 | "import/no-extraneous-dependencies": "off" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | public/bundle.* 4 | cypress/screenshots/ 5 | cypress/videos/ 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .circleci 2 | node_modules 3 | example 4 | public 5 | cypress 6 | cypress.json 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to svelte-notifications 2 | 3 | This document will go through how you can contribute to svelte-notifications 4 | 5 | ## Getting started 6 | 7 | ### 1. Fork the repository 8 | 9 | To start off, you need to fork this repository. This can be done by hitting the 10 | _Fork_ button in the upper right hand corner. 11 | 12 | ### 2. Clone your fork 13 | 14 | After forking the repository, you can now clone your fork to your local 15 | machine. 16 | 17 | ### 3. Install dependencies 18 | 19 | Before you can run the project, you need to install the dependencies. To do 20 | this, make sure you have [Node](https://nodejs.org/en/) installed. Then you can 21 | run this in the root of the project. 22 | 23 | npm install 24 | 25 | ## Running the project 26 | 27 | To run the project in development mode, you can start by runing this in the 28 | root of the project. 29 | 30 | npm run dev 31 | 32 | If the script runs successfully, you should now be able to visit 33 | [http://localhost:5000](http://localhost:5000) and see the development website. 34 | 35 | ## Submitting a pull request 36 | 37 | When you're done with fixing a issue or implementing a new feature, create a 38 | new commit, and push to your local fork. After that you can then go ahead and 39 | submit a PR to the original repository. 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Keenethics 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![build](https://img.shields.io/circleci/build/github/keenethics/svelte-notifications/master.svg) 2 | ![version](https://img.shields.io/github/package-json/v/keenethics/svelte-notifications.svg) 3 | ![license](https://img.shields.io/github/license/mashape/apistatus.svg) 4 | 5 | # Svelte notifications 6 | 7 | Simple and flexible notifications system for Svelte 3 8 | 9 | ![Svelte Notifications](https://github.com/keenethics/svelte-notifications/blob/media/svelte-notifications-preview.png?raw=true) 10 | 11 | ## Demonstration 12 | 13 | [https://svelte-notifications.netlify.com](https://svelte-notifications.netlify.com) 14 | 15 | ## Getting started 16 | 17 | ```bash 18 | npm install --save svelte-notifications 19 | ``` 20 | 21 | ## Basic usage 22 | 23 | ```svelte 24 | // MainComponent.svelte 25 | 26 | 31 | 32 | 33 | 34 | 35 | ``` 36 | 37 | ```svelte 38 | // ChildrenComponent.svelte 39 | 40 | 45 | 46 | 54 | ``` 55 | 56 | ## Providing custom notification component 57 | 58 | ```svelte 59 | // MainComponent.svelte 60 | 61 | 67 | 68 | 69 | 70 | 71 | ``` 72 | 73 | ```svelte 74 | // CustomNotification.svelte 75 | 76 | 85 | 86 |
87 |

{notification.heading}

88 |

{notification.description}

89 | 90 |
91 | ``` 92 | 93 | ```svelte 94 | // AnotherComponent.svelte 95 | 96 | 110 | 111 |
112 | 113 |
114 | ``` 115 | 116 | ## API 117 | 118 | #### `Notifications` 119 | 120 | The `Notifications` component supplies descendant components with notifications store through context. 121 | 122 | - @prop {component} `[item=null]` - Custom notification component that receives the notification object 123 | - @prop {boolean} `[withoutStyles=false]` - If you don't want to use the default styles, this flag will remove the classes to which the styles are attached 124 | - @prop {string|number} `[zIndex]` - Adds a style with z-index for the notification container 125 | 126 | ```svelte 127 | // MainComponent.svelte 128 | 129 | 134 | 135 | 136 | 137 | 138 | ``` 139 | 140 | #### `getNotificationsContext` 141 | 142 | A function that allows you to access the store and the functions that control the store. 143 | 144 | ```svelte 145 | // ChildrenComponent.svelte 146 | 147 | 159 | ``` 160 | 161 | #### `getNotificationsContext:addNotification` 162 | 163 | You can provide any object that the notification component will receive. The default object looks like this: 164 | 165 | - @param {Object} `notification` - The object that will receive the notification component 166 | - @param {string} `[id=timestamp-rand]` - Unique notification identificator 167 | - @param {string} `text` – Notification text 168 | - @param {string} `[position=bottom-center]` – One of these values: `top-left`, `top-center`, `top-right`, `bottom-left`, `bottom-center`, `bottom-right` 169 | - @param {string} `type` – One of these values: `success`, `warning`, `error` 170 | - @param {number} `[removeAfter]` – After how much the notification will disappear (in milliseconds) 171 | 172 | ```svelte 173 | // ChildrenComponent.svelte 174 | 175 | 189 | ``` 190 | 191 | #### `getNotificationsContext:removeNotification` 192 | 193 | - @param {string} `id` - Unique notification identificator 194 | 195 | ```svelte 196 | // ChildrenComponent.svelte 197 | 198 | 205 | ``` 206 | 207 | #### `getNotificationsContext:clearNotifications` 208 | 209 | ```svelte 210 | // ChildrenComponent.svelte 211 | 212 | 219 | ``` 220 | 221 | #### `getNotificationsContext:subscribe` 222 | 223 | Default Svelte subscribe method that allows interested parties to be notified whenever the store value changes 224 | 225 | ## Contributing 226 | 227 | Read more about contributing [here](/CONTRIBUTING.md) 228 | -------------------------------------------------------------------------------- /build/bundle.css: -------------------------------------------------------------------------------- 1 | .default-position-style-top-left.svelte-inamvt,.default-position-style-top-center.svelte-inamvt,.default-position-style-top-right.svelte-inamvt,.default-position-style-bottom-left.svelte-inamvt,.default-position-style-bottom-center.svelte-inamvt,.default-position-style-bottom-right.svelte-inamvt{position:fixed;max-width:400px}.default-position-style-top-left.svelte-inamvt{top:0;left:0}.default-position-style-top-center.svelte-inamvt{top:0;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.default-position-style-top-right.svelte-inamvt{top:0;right:0}.default-position-style-bottom-left.svelte-inamvt{bottom:0;left:0}.default-position-style-bottom-center.svelte-inamvt{bottom:0;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.default-position-style-bottom-right.svelte-inamvt{bottom:0;right:0}.default-notification-style.svelte-ue5kxf.svelte-ue5kxf{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;margin:12px;background:#fff;color:#000;border-radius:6px;-webkit-box-shadow:0 4px 10px rgba(0, 0, 0, 0.08);box-shadow:0 4px 10px rgba(0, 0, 0, 0.08);min-height:0;min-width:200px;overflow:hidden}.default-notification-style-content.svelte-ue5kxf.svelte-ue5kxf{width:210px;padding:12px 6px 12px 12px;-webkit-box-sizing:border-box;box-sizing:border-box;word-wrap:break-word}.default-notification-style-button.svelte-ue5kxf.svelte-ue5kxf{display:block;width:40px;padding:0;margin:0;border:none;border-left:1px solid #eee;outline:none;background:none;cursor:pointer;font-size:20px;color:#000;-webkit-box-sizing:border-box;box-sizing:border-box}.default-notification-style-button.svelte-ue5kxf.svelte-ue5kxf:hover{background:rgba(0, 0, 0, 0.01)}.default-notification-error.svelte-ue5kxf.svelte-ue5kxf{background:#f3555a;color:#fff}.default-notification-error.svelte-ue5kxf .default-notification-style-button.svelte-ue5kxf{border-left:1px solid rgba(255, 255, 255, 0.4);color:#fff}.default-notification-warning.svelte-ue5kxf.svelte-ue5kxf{background:#ffb900;color:#000}.default-notification-warning.svelte-ue5kxf .default-notification-style-button.svelte-ue5kxf{border-left:1px solid rgba(0, 0, 0, 0.2);color:#000}.default-notification-success.svelte-ue5kxf.svelte-ue5kxf{background:#22ce6c;color:#fff}.default-notification-success.svelte-ue5kxf .default-notification-style-button.svelte-ue5kxf{border-left:1px solid rgba(255, 255, 255, 0.4);color:#fff} -------------------------------------------------------------------------------- /build/bundle.js: -------------------------------------------------------------------------------- 1 | !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).Notifications={})}(this,(function(t){"use strict";function n(){}const e=t=>t;function o(t){return t()}function i(){return Object.create(null)}function r(t){t.forEach(o)}function c(t){return"function"==typeof t}function s(t,n){return t!=t?n==n:t!==n||t&&"object"==typeof t||"function"==typeof t}function u(t,e,o){t.$$.on_destroy.push(function(t,...e){if(null==t)return n;const o=t.subscribe(...e);return o.unsubscribe?()=>o.unsubscribe():o}(e,o))}function l(t,n,e,o){if(t){const i=f(t,n,e,o);return t[0](i)}}function f(t,n,e,o){return t[1]&&o?function(t,n){for(const e in n)t[e]=n[e];return t}(e.ctx.slice(),t[1](o(n))):e.ctx}function a(t,n,e,o){if(t[2]&&o){const i=t[2](o(e));if(void 0===n.dirty)return i;if("object"==typeof i){const t=[],e=Math.max(n.dirty.length,i.length);for(let o=0;o32){const n=[],e=t.ctx.length/32;for(let t=0;twindow.performance.now():()=>Date.now(),g=p?t=>requestAnimationFrame(t):n;const y=new Set;function b(t){y.forEach((n=>{n.c(t)||(y.delete(n),n.f())})),0!==y.size&&g(b)}function v(t){let n;return 0===y.size&&g(b),{promise:new Promise((e=>{y.add(n={c:t,f:e})})),abort(){y.delete(n)}}}function w(t,n){t.appendChild(n)}function x(t){if(!t)return document;const n=t.getRootNode?t.getRootNode():t.ownerDocument;return n&&n.host?n:t.ownerDocument}function _(t){const n=N("style");return function(t,n){w(t.head||t,n),n.sheet}(x(t),n),n.sheet}function k(t,n,e){t.insertBefore(n,e||null)}function S(t){t.parentNode.removeChild(t)}function N(t){return document.createElement(t)}function E(t){return document.createTextNode(t)}function z(){return E(" ")}function M(){return E("")}function R(t,n,e){null==e?t.removeAttribute(n):t.getAttribute(n)!==e&&t.setAttribute(n,e)}function j(t,n,e,o){null===e?t.style.removeProperty(n):t.style.setProperty(n,e,o?"important":"")}function A(t,n){return new t(n)}const C=new Map;let O,P=0;function T(t,n,e,o,i,r,c,s=0){const u=16.666/o;let l="{\n";for(let t=0;t<=1;t+=u){const o=n+(e-n)*r(t);l+=100*t+`%{${c(o,1-o)}}\n`}const f=l+`100% {${c(e,1-e)}}\n}`,a=`__svelte_${function(t){let n=5381,e=t.length;for(;e--;)n=(n<<5)-n^t.charCodeAt(e);return n>>>0}(f)}_${s}`,d=x(t),{stylesheet:$,rules:h}=C.get(d)||function(t,n){const e={stylesheet:_(n),rules:{}};return C.set(t,e),e}(d,t);h[a]||(h[a]=!0,$.insertRule(`@keyframes ${a} ${f}`,$.cssRules.length));const p=t.style.animation||"";return t.style.animation=`${p?`${p}, `:""}${a} ${o}ms linear ${i}ms 1 both`,P+=1,a}function D(t,n){const e=(t.style.animation||"").split(", "),o=e.filter(n?t=>t.indexOf(n)<0:t=>-1===t.indexOf("__svelte")),i=e.length-o.length;i&&(t.style.animation=o.join(", "),P-=i,P||g((()=>{P||(C.forEach((t=>{const{ownerNode:n}=t.stylesheet;n&&S(n)})),C.clear())})))}function I(t){O=t}function q(){if(!O)throw new Error("Function called outside component initialization");return O}const B=[],F=[],L=[],G=[],H=Promise.resolve();let J=!1;function K(t){L.push(t)}const Q=new Set;let U,V=0;function W(){const t=O;do{for(;V{U=null}))),U}function Z(t,n,e){t.dispatchEvent(function(t,n,{bubbles:e=!1,cancelable:o=!1}={}){const i=document.createEvent("CustomEvent");return i.initCustomEvent(t,e,o,n),i}(`${n?"intro":"outro"}${e}`))}const tt=new Set;let nt;function et(){nt={r:0,c:[],p:nt}}function ot(){nt.r||r(nt.c),nt=nt.p}function it(t,n){t&&t.i&&(tt.delete(t),t.i(n))}function rt(t,n,e,o){if(t&&t.o){if(tt.has(t))return;tt.add(t),nt.c.push((()=>{tt.delete(t),o&&(e&&t.d(1),o())})),t.o(n)}else o&&o()}const ct={duration:0};function st(t,n){rt(t,1,1,(()=>{n.delete(t.key)}))}function ut(t){t&&t.c()}function lt(t,n,e,i){const{fragment:s,after_update:u}=t.$$;s&&s.m(n,e),i||K((()=>{const n=t.$$.on_mount.map(o).filter(c);t.$$.on_destroy?t.$$.on_destroy.push(...n):r(n),t.$$.on_mount=[]})),u.forEach(K)}function ft(t,n){const e=t.$$;null!==e.fragment&&(r(e.on_destroy),e.fragment&&e.fragment.d(n),e.on_destroy=e.fragment=null,e.ctx=[])}function at(t,n){-1===t.$$.dirty[0]&&(B.push(t),J||(J=!0,H.then(W)),t.$$.dirty.fill(0)),t.$$.dirty[n/31|0]|=1<{const i=o.length?o[0]:e;return d.ctx&&s(d.ctx[n],d.ctx[n]=i)&&(!d.skip_bound&&d.bound[n]&&d.bound[n](i),$&&at(t,n)),e})):[],d.update(),$=!0,r(d.before_update),d.fragment=!!c&&c(d.ctx),e.target){if(e.hydrate){const t=function(t){return Array.from(t.childNodes)}(e.target);d.fragment&&d.fragment.l(t),t.forEach(S)}else d.fragment&&d.fragment.c();e.intro&&it(t.$$.fragment),lt(t,e.target,e.anchor,e.customElement),W()}I(a)}class $t{$destroy(){ft(this,1),this.$destroy=n}$on(t,e){if(!c(e))return n;const o=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return o.push(e),()=>{const t=o.indexOf(e);-1!==t&&o.splice(t,1)}}$set(t){var n;this.$$set&&(n=t,0!==Object.keys(n).length)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}}const ht={subscribe:null,addNotification:null,removeNotification:null,clearNotifications:null},pt=()=>{return t=ht,q().$$.context.get(t);var t};function mt(t){let n,e,o;var i=t[0];function r(t){return{props:{notification:t[1],withoutStyles:t[2],onRemove:t[3]}}}return i&&(n=A(i,r(t))),{c(){n&&ut(n.$$.fragment),e=M()},m(t,i){n&<(n,t,i),k(t,e,i),o=!0},p(t,[o]){const c={};if(2&o&&(c.notification=t[1]),4&o&&(c.withoutStyles=t[2]),i!==(i=t[0])){if(n){et();const t=n;rt(t.$$.fragment,1,0,(()=>{ft(t,1)})),ot()}i?(n=A(i,r(t)),ut(n.$$.fragment),it(n.$$.fragment,1),lt(n,e.parentNode,e)):n=null}else i&&n.$set(c)},i(t){o||(n&&it(n.$$.fragment,t),o=!0)},o(t){n&&rt(n.$$.fragment,t),o=!1},d(t){t&&S(e),n&&ft(n,t)}}}function gt(t,n,e){let{item:o}=n,{notification:i={}}=n,{withoutStyles:r=!1}=n;const{removeNotification:c}=pt(),{id:s,removeAfter:u}=i,l=()=>c(s);let f=null;var a;return u&&(f=setTimeout(l,u)),a=()=>{u&&f&&clearTimeout(f)},q().$$.on_destroy.push(a),t.$$set=t=>{"item"in t&&e(0,o=t.item),"notification"in t&&e(1,i=t.notification),"withoutStyles"in t&&e(2,r=t.withoutStyles)},[o,i,r,l]}class yt extends $t{constructor(t){super(),dt(this,t,gt,mt,s,{item:0,notification:1,withoutStyles:2})}}function bt(t,{delay:n=0,duration:o=400,easing:i=e}={}){const r=+getComputedStyle(t).opacity;return{delay:n,duration:o,easing:i,css:t=>"opacity: "+t*r}}function vt(t){let o,i,s,u,f,p,g,y,b,x;const _=t[6].default,M=l(_,t,t[5],null),j=M||function(t){let e;return{c(){e=E(t[1])},m(t,n){k(t,e,n)},p:n,d(t){t&&S(e)}}}(t);return{c(){o=N("div"),i=N("div"),j&&j.c(),s=z(),u=N("button"),f=E("×"),R(i,"class",h(t[2]("content"))+" svelte-ue5kxf"),R(u,"class",h(t[2]("button"))+" svelte-ue5kxf"),R(u,"aria-label","delete notification"),R(o,"class",h(t[2]())+" svelte-ue5kxf"),R(o,"role","status"),R(o,"aria-live","polite")},m(n,e){var r,l,a,d;k(n,o,e),w(o,i),j&&j.m(i,null),w(o,s),w(o,u),w(u,f),y=!0,b||(l="click",a=function(){c(t[0])&&t[0].apply(this,arguments)},(r=u).addEventListener(l,a,d),x=()=>r.removeEventListener(l,a,d),b=!0)},p(n,e){t=n,M&&M.p&&(!y||32&e)&&d(M,_,t,t[5],y?a(_,t[5],e,null):$(t[5]),null)},i(t){y||(it(j,t),K((()=>{g&&g.end(1),p=function(t,o,i){let r,s,u=o(t,i),l=!1,f=0;function a(){r&&D(t,r)}function d(){const{delay:o=0,duration:i=300,easing:c=e,tick:d=n,css:$}=u||ct;$&&(r=T(t,0,1,i,o,c,$,f++)),d(0,1);const h=m()+o,p=h+i;s&&s.abort(),l=!0,K((()=>Z(t,!0,"start"))),s=v((n=>{if(l){if(n>=p)return d(1,0),Z(t,!0,"end"),a(),l=!1;if(n>=h){const t=c((n-h)/i);d(t,1-t)}}return l}))}let $=!1;return{start(){$||($=!0,D(t),c(u)?(u=u(),Y().then(d)):d())},invalidate(){$=!1},end(){l&&(a(),l=!1)}}}(o,bt,{}),p.start()})),y=!0)},o(t){rt(j,t),p&&p.invalidate(),g=function(t,o,i){let s,u=o(t,i),l=!0;const f=nt;function a(){const{delay:o=0,duration:i=300,easing:c=e,tick:a=n,css:d}=u||ct;d&&(s=T(t,1,0,i,o,c,d));const $=m()+o,h=$+i;K((()=>Z(t,!1,"start"))),v((n=>{if(l){if(n>=h)return a(0,1),Z(t,!1,"end"),--f.r||r(f.c),!1;if(n>=$){const t=c((n-$)/i);a(1-t,t)}}return l}))}return f.r+=1,c(u)?Y().then((()=>{u=u(),a()})):a(),{end(n){n&&u.tick&&u.tick(1,0),l&&(s&&D(t,s),l=!1)}}}(o,bt,{}),y=!1},d(t){t&&S(o),j&&j.d(t),t&&g&&g.end(),b=!1,x()}}}function wt(t){let n,e,o=t[1]&&vt(t);return{c(){o&&o.c(),n=M()},m(t,i){o&&o.m(t,i),k(t,n,i),e=!0},p(t,[n]){t[1]&&o.p(t,n)},i(t){e||(it(o),e=!0)},o(t){rt(o),e=!1},d(t){o&&o.d(t),t&&S(n)}}}function xt(t,n,e){let{$$slots:o={},$$scope:i}=n,{notification:r={}}=n,{withoutStyles:c=!1}=n,{onRemove:s=null}=n;const{text:u,type:l}=r;return t.$$set=t=>{"notification"in t&&e(3,r=t.notification),"withoutStyles"in t&&e(4,c=t.withoutStyles),"onRemove"in t&&e(0,s=t.onRemove),"$$scope"in t&&e(5,i=t.$$scope)},[s,u,t=>{const n=t?`-${t}`:"";return`notification${n}${c?"":` default-notification-style${n}`}${l&&!t?` default-notification-${l}`:""}`},r,c,i,o]}class _t extends $t{constructor(t){super(),dt(this,t,xt,wt,s,{notification:3,withoutStyles:4,onRemove:0})}}const kt=[];const St=["top-left","top-center","top-right","bottom-left","bottom-center","bottom-right"];var Nt=(()=>{const t=function(t,e=n){let o;const i=new Set;function r(n){if(s(t,n)&&(t=n,o)){const n=!kt.length;for(const n of i)n[1](),kt.push(n,t);if(n){for(let t=0;t{i.delete(u),0===i.size&&(o(),o=null)}}}}([]);return{subscribe:t.subscribe,addNotification:n=>((t,n)=>{if(!t)return;const{update:e}=n,o={id:`${(new Date).getTime()}-${Math.floor(9999*Math.random())}`,position:"bottom-center",text:"",...t};St.includes(t.position)&&e((t=>o.position.includes("top-")?[o,...t]:[...t,o]))})(n,t),removeNotification:n=>((t,{update:n})=>{t&&n((n=>n.filter((({id:n})=>n!==t))))})(n,t),clearNotifications:()=>(t=>t.set([]))(t)}})();function Et(t,n,e){const o=t.slice();return o[7]=n[e],o}function zt(t,n,e){const o=t.slice();return o[10]=n[e],o}function Mt(t){let n,e;return n=new yt({props:{notification:t[10],withoutStyles:t[1],item:t[0]||_t}}),{c(){ut(n.$$.fragment)},m(t,o){lt(n,t,o),e=!0},p(t,e){const o={};8&e&&(o.notification=t[10]),2&e&&(o.withoutStyles=t[1]),1&e&&(o.item=t[0]||_t),n.$set(o)},i(t){e||(it(n.$$.fragment,t),e=!0)},o(t){rt(n.$$.fragment,t),e=!1},d(t){ft(n,t)}}}function Rt(t,n){let e,o,i,r=n[10].position===n[7]&&Mt(n);return{key:t,first:null,c(){e=M(),r&&r.c(),o=M(),this.first=e},m(t,n){k(t,e,n),r&&r.m(t,n),k(t,o,n),i=!0},p(t,e){(n=t)[10].position===n[7]?r?(r.p(n,e),8&e&&it(r,1)):(r=Mt(n),r.c(),it(r,1),r.m(o.parentNode,o)):r&&(et(),rt(r,1,1,(()=>{r=null})),ot())},i(t){i||(it(r),i=!0)},o(t){rt(r),i=!1},d(t){t&&S(e),r&&r.d(t),t&&S(o)}}}function jt(t){let n,e,o,i=[],r=new Map,c=t[3];const s=t=>t[10].id;for(let n=0;ny.get(i)?(v.add(o),w(n)):(b.add(i),d--):(u(e,c),d--)}for(;d--;){const n=t[d];g.has(n.key)||u(n,c)}for(;$;)w(m[$-1]);return m}(i,o,s,1,t,c,r,n,st,Rt,e,zt),ot()),4&o&&j(n,"z-index",t[2])},i(t){if(!o){for(let t=0;trt(s[t],1,1,(()=>{s[t]=null}));return{c(){r&&r.c(),n=z(),e=N("div");for(let t=0;te(3,o=t)));let{$$slots:i={},$$scope:r}=n,{item:c=null}=n,{withoutStyles:s=!1}=n,{zIndex:l=null}=n;return function(t,n){q().$$.context.set(t,n)}(ht,Nt),t.$$set=t=>{"item"in t&&e(0,c=t.item),"withoutStyles"in t&&e(1,s=t.withoutStyles),"zIndex"in t&&e(2,l=t.zIndex),"$$scope"in t&&e(5,r=t.$$scope)},[c,s,l,o,(t="")=>`position-${t}${s?"":` default-position-style-${t}`}`,r,i]}t.default=class extends $t{constructor(t){super(),dt(this,t,Ct,At,s,{item:0,withoutStyles:1,zIndex:2})}},t.getNotificationsContext=pt,Object.defineProperty(t,"__esModule",{value:!0})})); 2 | -------------------------------------------------------------------------------- /build/bundle.mjs: -------------------------------------------------------------------------------- 1 | function t(){}const n=t=>t;function e(t){return t()}function o(){return Object.create(null)}function i(t){t.forEach(e)}function r(t){return"function"==typeof t}function c(t,n){return t!=t?n==n:t!==n||t&&"object"==typeof t||"function"==typeof t}function s(n,e,o){n.$$.on_destroy.push(function(n,...e){if(null==n)return t;const o=n.subscribe(...e);return o.unsubscribe?()=>o.unsubscribe():o}(e,o))}function u(t,n,e,o){if(t){const i=l(t,n,e,o);return t[0](i)}}function l(t,n,e,o){return t[1]&&o?function(t,n){for(const e in n)t[e]=n[e];return t}(e.ctx.slice(),t[1](o(n))):e.ctx}function f(t,n,e,o){if(t[2]&&o){const i=t[2](o(e));if(void 0===n.dirty)return i;if("object"==typeof i){const t=[],e=Math.max(n.dirty.length,i.length);for(let o=0;o32){const n=[],e=t.ctx.length/32;for(let t=0;twindow.performance.now():()=>Date.now(),p=h?t=>requestAnimationFrame(t):t;const g=new Set;function y(t){g.forEach((n=>{n.c(t)||(g.delete(n),n.f())})),0!==g.size&&p(y)}function b(t){let n;return 0===g.size&&p(y),{promise:new Promise((e=>{g.add(n={c:t,f:e})})),abort(){g.delete(n)}}}function w(t,n){t.appendChild(n)}function v(t){if(!t)return document;const n=t.getRootNode?t.getRootNode():t.ownerDocument;return n&&n.host?n:t.ownerDocument}function x(t){const n=S("style");return function(t,n){w(t.head||t,n),n.sheet}(v(t),n),n.sheet}function _(t,n,e){t.insertBefore(n,e||null)}function k(t){t.parentNode.removeChild(t)}function S(t){return document.createElement(t)}function E(t){return document.createTextNode(t)}function N(){return E(" ")}function z(){return E("")}function M(t,n,e){null==e?t.removeAttribute(n):t.getAttribute(n)!==e&&t.setAttribute(n,e)}function R(t,n,e,o){null===e?t.style.removeProperty(n):t.style.setProperty(n,e,o?"important":"")}function A(t,n){return new t(n)}const C=new Map;let j,O=0;function P(t,n,e,o,i,r,c,s=0){const u=16.666/o;let l="{\n";for(let t=0;t<=1;t+=u){const o=n+(e-n)*r(t);l+=100*t+`%{${c(o,1-o)}}\n`}const f=l+`100% {${c(e,1-e)}}\n}`,a=`__svelte_${function(t){let n=5381,e=t.length;for(;e--;)n=(n<<5)-n^t.charCodeAt(e);return n>>>0}(f)}_${s}`,d=v(t),{stylesheet:$,rules:h}=C.get(d)||function(t,n){const e={stylesheet:x(n),rules:{}};return C.set(t,e),e}(d,t);h[a]||(h[a]=!0,$.insertRule(`@keyframes ${a} ${f}`,$.cssRules.length));const m=t.style.animation||"";return t.style.animation=`${m?`${m}, `:""}${a} ${o}ms linear ${i}ms 1 both`,O+=1,a}function D(t,n){const e=(t.style.animation||"").split(", "),o=e.filter(n?t=>t.indexOf(n)<0:t=>-1===t.indexOf("__svelte")),i=e.length-o.length;i&&(t.style.animation=o.join(", "),O-=i,O||p((()=>{O||(C.forEach((t=>{const{ownerNode:n}=t.stylesheet;n&&k(n)})),C.clear())})))}function I(t){j=t}function T(){if(!j)throw new Error("Function called outside component initialization");return j}const q=[],B=[],F=[],L=[],G=Promise.resolve();let H=!1;function J(t){F.push(t)}const K=new Set;let Q,U=0;function V(){const t=j;do{for(;U{Q=null}))),Q}function Y(t,n,e){t.dispatchEvent(function(t,n,{bubbles:e=!1,cancelable:o=!1}={}){const i=document.createEvent("CustomEvent");return i.initCustomEvent(t,e,o,n),i}(`${n?"intro":"outro"}${e}`))}const Z=new Set;let tt;function nt(){tt={r:0,c:[],p:tt}}function et(){tt.r||i(tt.c),tt=tt.p}function ot(t,n){t&&t.i&&(Z.delete(t),t.i(n))}function it(t,n,e,o){if(t&&t.o){if(Z.has(t))return;Z.add(t),tt.c.push((()=>{Z.delete(t),o&&(e&&t.d(1),o())})),t.o(n)}else o&&o()}const rt={duration:0};function ct(t,n){it(t,1,1,(()=>{n.delete(t.key)}))}function st(t){t&&t.c()}function ut(t,n,o,c){const{fragment:s,after_update:u}=t.$$;s&&s.m(n,o),c||J((()=>{const n=t.$$.on_mount.map(e).filter(r);t.$$.on_destroy?t.$$.on_destroy.push(...n):i(n),t.$$.on_mount=[]})),u.forEach(J)}function lt(t,n){const e=t.$$;null!==e.fragment&&(i(e.on_destroy),e.fragment&&e.fragment.d(n),e.on_destroy=e.fragment=null,e.ctx=[])}function ft(t,n){-1===t.$$.dirty[0]&&(q.push(t),H||(H=!0,G.then(V)),t.$$.dirty.fill(0)),t.$$.dirty[n/31|0]|=1<{const i=o.length?o[0]:e;return d.ctx&&s(d.ctx[t],d.ctx[t]=i)&&(!d.skip_bound&&d.bound[t]&&d.bound[t](i),$&&ft(n,t)),e})):[],d.update(),$=!0,i(d.before_update),d.fragment=!!c&&c(d.ctx),e.target){if(e.hydrate){const t=function(t){return Array.from(t.childNodes)}(e.target);d.fragment&&d.fragment.l(t),t.forEach(k)}else d.fragment&&d.fragment.c();e.intro&&ot(n.$$.fragment),ut(n,e.target,e.anchor,e.customElement),V()}I(a)}class dt{$destroy(){lt(this,1),this.$destroy=t}$on(n,e){if(!r(e))return t;const o=this.$$.callbacks[n]||(this.$$.callbacks[n]=[]);return o.push(e),()=>{const t=o.indexOf(e);-1!==t&&o.splice(t,1)}}$set(t){var n;this.$$set&&(n=t,0!==Object.keys(n).length)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}}const $t={subscribe:null,addNotification:null,removeNotification:null,clearNotifications:null},ht=()=>{return t=$t,T().$$.context.get(t);var t};function mt(t){let n,e,o;var i=t[0];function r(t){return{props:{notification:t[1],withoutStyles:t[2],onRemove:t[3]}}}return i&&(n=A(i,r(t))),{c(){n&&st(n.$$.fragment),e=z()},m(t,i){n&&ut(n,t,i),_(t,e,i),o=!0},p(t,[o]){const c={};if(2&o&&(c.notification=t[1]),4&o&&(c.withoutStyles=t[2]),i!==(i=t[0])){if(n){nt();const t=n;it(t.$$.fragment,1,0,(()=>{lt(t,1)})),et()}i?(n=A(i,r(t)),st(n.$$.fragment),ot(n.$$.fragment,1),ut(n,e.parentNode,e)):n=null}else i&&n.$set(c)},i(t){o||(n&&ot(n.$$.fragment,t),o=!0)},o(t){n&&it(n.$$.fragment,t),o=!1},d(t){t&&k(e),n&<(n,t)}}}function pt(t,n,e){let{item:o}=n,{notification:i={}}=n,{withoutStyles:r=!1}=n;const{removeNotification:c}=ht(),{id:s,removeAfter:u}=i,l=()=>c(s);let f=null;var a;return u&&(f=setTimeout(l,u)),a=()=>{u&&f&&clearTimeout(f)},T().$$.on_destroy.push(a),t.$$set=t=>{"item"in t&&e(0,o=t.item),"notification"in t&&e(1,i=t.notification),"withoutStyles"in t&&e(2,r=t.withoutStyles)},[o,i,r,l]}class gt extends dt{constructor(t){super(),at(this,t,pt,mt,c,{item:0,notification:1,withoutStyles:2})}}function yt(t,{delay:e=0,duration:o=400,easing:i=n}={}){const r=+getComputedStyle(t).opacity;return{delay:e,duration:o,easing:i,css:t=>"opacity: "+t*r}}function bt(e){let o,c,s,l,h,p,g,y,v,x;const z=e[6].default,R=u(z,e,e[5],null),A=R||function(n){let e;return{c(){e=E(n[1])},m(t,n){_(t,e,n)},p:t,d(t){t&&k(e)}}}(e);return{c(){o=S("div"),c=S("div"),A&&A.c(),s=N(),l=S("button"),h=E("×"),M(c,"class",$(e[2]("content"))+" svelte-ue5kxf"),M(l,"class",$(e[2]("button"))+" svelte-ue5kxf"),M(l,"aria-label","delete notification"),M(o,"class",$(e[2]())+" svelte-ue5kxf"),M(o,"role","status"),M(o,"aria-live","polite")},m(t,n){var i,u,f,a;_(t,o,n),w(o,c),A&&A.m(c,null),w(o,s),w(o,l),w(l,h),y=!0,v||(u="click",f=function(){r(e[0])&&e[0].apply(this,arguments)},(i=l).addEventListener(u,f,a),x=()=>i.removeEventListener(u,f,a),v=!0)},p(t,n){e=t,R&&R.p&&(!y||32&n)&&a(R,z,e,e[5],y?f(z,e[5],n,null):d(e[5]),null)},i(e){y||(ot(A,e),J((()=>{g&&g.end(1),p=function(e,o,i){let c,s,u=o(e,i),l=!1,f=0;function a(){c&&D(e,c)}function d(){const{delay:o=0,duration:i=300,easing:r=n,tick:d=t,css:$}=u||rt;$&&(c=P(e,0,1,i,o,r,$,f++)),d(0,1);const h=m()+o,p=h+i;s&&s.abort(),l=!0,J((()=>Y(e,!0,"start"))),s=b((t=>{if(l){if(t>=p)return d(1,0),Y(e,!0,"end"),a(),l=!1;if(t>=h){const n=r((t-h)/i);d(n,1-n)}}return l}))}let $=!1;return{start(){$||($=!0,D(e),r(u)?(u=u(),X().then(d)):d())},invalidate(){$=!1},end(){l&&(a(),l=!1)}}}(o,yt,{}),p.start()})),y=!0)},o(e){it(A,e),p&&p.invalidate(),g=function(e,o,c){let s,u=o(e,c),l=!0;const f=tt;function a(){const{delay:o=0,duration:r=300,easing:c=n,tick:a=t,css:d}=u||rt;d&&(s=P(e,1,0,r,o,c,d));const $=m()+o,h=$+r;J((()=>Y(e,!1,"start"))),b((t=>{if(l){if(t>=h)return a(0,1),Y(e,!1,"end"),--f.r||i(f.c),!1;if(t>=$){const n=c((t-$)/r);a(1-n,n)}}return l}))}return f.r+=1,r(u)?X().then((()=>{u=u(),a()})):a(),{end(t){t&&u.tick&&u.tick(1,0),l&&(s&&D(e,s),l=!1)}}}(o,yt,{}),y=!1},d(t){t&&k(o),A&&A.d(t),t&&g&&g.end(),v=!1,x()}}}function wt(t){let n,e,o=t[1]&&bt(t);return{c(){o&&o.c(),n=z()},m(t,i){o&&o.m(t,i),_(t,n,i),e=!0},p(t,[n]){t[1]&&o.p(t,n)},i(t){e||(ot(o),e=!0)},o(t){it(o),e=!1},d(t){o&&o.d(t),t&&k(n)}}}function vt(t,n,e){let{$$slots:o={},$$scope:i}=n,{notification:r={}}=n,{withoutStyles:c=!1}=n,{onRemove:s=null}=n;const{text:u,type:l}=r;return t.$$set=t=>{"notification"in t&&e(3,r=t.notification),"withoutStyles"in t&&e(4,c=t.withoutStyles),"onRemove"in t&&e(0,s=t.onRemove),"$$scope"in t&&e(5,i=t.$$scope)},[s,u,t=>{const n=t?`-${t}`:"";return`notification${n}${c?"":` default-notification-style${n}`}${l&&!t?` default-notification-${l}`:""}`},r,c,i,o]}class xt extends dt{constructor(t){super(),at(this,t,vt,wt,c,{notification:3,withoutStyles:4,onRemove:0})}}const _t=[];const kt=["top-left","top-center","top-right","bottom-left","bottom-center","bottom-right"];var St=(()=>{const n=function(n,e=t){let o;const i=new Set;function r(t){if(c(n,t)&&(n=t,o)){const t=!_t.length;for(const t of i)t[1](),_t.push(t,n);if(t){for(let t=0;t<_t.length;t+=2)_t[t][0](_t[t+1]);_t.length=0}}}return{set:r,update:function(t){r(t(n))},subscribe:function(c,s=t){const u=[c,s];return i.add(u),1===i.size&&(o=e(r)||t),c(n),()=>{i.delete(u),0===i.size&&(o(),o=null)}}}}([]);return{subscribe:n.subscribe,addNotification:t=>((t,n)=>{if(!t)return;const{update:e}=n,o={id:`${(new Date).getTime()}-${Math.floor(9999*Math.random())}`,position:"bottom-center",text:"",...t};kt.includes(t.position)&&e((t=>o.position.includes("top-")?[o,...t]:[...t,o]))})(t,n),removeNotification:t=>((t,{update:n})=>{t&&n((n=>n.filter((({id:n})=>n!==t))))})(t,n),clearNotifications:()=>(t=>t.set([]))(n)}})();function Et(t,n,e){const o=t.slice();return o[7]=n[e],o}function Nt(t,n,e){const o=t.slice();return o[10]=n[e],o}function zt(t){let n,e;return n=new gt({props:{notification:t[10],withoutStyles:t[1],item:t[0]||xt}}),{c(){st(n.$$.fragment)},m(t,o){ut(n,t,o),e=!0},p(t,e){const o={};8&e&&(o.notification=t[10]),2&e&&(o.withoutStyles=t[1]),1&e&&(o.item=t[0]||xt),n.$set(o)},i(t){e||(ot(n.$$.fragment,t),e=!0)},o(t){it(n.$$.fragment,t),e=!1},d(t){lt(n,t)}}}function Mt(t,n){let e,o,i,r=n[10].position===n[7]&&zt(n);return{key:t,first:null,c(){e=z(),r&&r.c(),o=z(),this.first=e},m(t,n){_(t,e,n),r&&r.m(t,n),_(t,o,n),i=!0},p(t,e){(n=t)[10].position===n[7]?r?(r.p(n,e),8&e&&ot(r,1)):(r=zt(n),r.c(),ot(r,1),r.m(o.parentNode,o)):r&&(nt(),it(r,1,1,(()=>{r=null})),et())},i(t){i||(ot(r),i=!0)},o(t){it(r),i=!1},d(t){t&&k(e),r&&r.d(t),t&&k(o)}}}function Rt(t){let n,e,o,i=[],r=new Map,c=t[3];const s=t=>t[10].id;for(let n=0;ny.get(i)?(w.add(o),v(n)):(b.add(i),d--):(u(e,c),d--)}for(;d--;){const n=t[d];g.has(n.key)||u(n,c)}for(;$;)v(p[$-1]);return p}(i,o,s,1,t,c,r,n,ct,Mt,e,Nt),et()),4&o&&R(n,"z-index",t[2])},i(t){if(!o){for(let t=0;tit(s[t],1,1,(()=>{s[t]=null}));return{c(){r&&r.c(),n=N(),e=S("div");for(let t=0;te(3,o=t)));let{$$slots:i={},$$scope:r}=n,{item:c=null}=n,{withoutStyles:u=!1}=n,{zIndex:l=null}=n;return function(t,n){T().$$.context.set(t,n)}($t,St),t.$$set=t=>{"item"in t&&e(0,c=t.item),"withoutStyles"in t&&e(1,u=t.withoutStyles),"zIndex"in t&&e(2,l=t.zIndex),"$$scope"in t&&e(5,r=t.$$scope)},[c,u,l,o,(t="")=>`position-${t}${u?"":` default-position-style-${t}`}`,r,i]}class jt extends dt{constructor(t){super(),at(this,t,Ct,At,c,{item:0,withoutStyles:1,zIndex:2})}}export{jt as default,ht as getNotificationsContext}; 2 | -------------------------------------------------------------------------------- /cypress.config.cjs: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress'); 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | baseUrl: 'http://localhost:5000', 6 | video: false, 7 | supportFile: 'cypress/support/index.js', 8 | specPattern: 'cypress/integration/*.spec.js', 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /cypress/integration/notifications.spec.js: -------------------------------------------------------------------------------- 1 | const positionButtonBottomCenter = '#bottom-center'; 2 | const positionButtonBottomRight = '#bottom-right'; 3 | const positionButtonBottomLeft = '#bottom-left'; 4 | const positionButtonTopCenter = '#top-center'; 5 | const positionButtonTopRight = '#top-right'; 6 | const positionButtonTopLeft = '#top-left'; 7 | const notificationBottomCenter = '.position-bottom-center .notification .notification-content'; 8 | const notificationBottomRight = '.position-bottom-right .notification .notification-content'; 9 | const notificationBottomLeft = '.position-bottom-left .notification .notification-content'; 10 | const notificationTopCenter = '.position-top-center .notification .notification-content'; 11 | const notificationTopRight = '.position-top-right .notification .notification-content'; 12 | const notificationTopLeft = '.position-top-left .notification .notification-content'; 13 | const notificationInput = '#notification-text'; 14 | const timerInput = '#notification-remove-after'; 15 | const deleteNotificationButton = 'button[aria-label="delete notification"]'; 16 | const fakeText = Math.random().toString(36).substring(2, 15); 17 | 18 | const createNotification = (position) => { 19 | cy.get(position).click(); 20 | cy.contains('button', 'Create').click(); 21 | }; 22 | 23 | const deleteNotification = () => { 24 | cy.get(deleteNotificationButton).click(); 25 | cy.contains(notificationBottomCenter, `${fakeText} bottom center`).should('not.exist'); 26 | }; 27 | 28 | const visit = () => { 29 | cy.visit(Cypress.config().baseUrl); 30 | }; 31 | 32 | describe('Notifications', () => { 33 | describe('Default notifications are displayed correctly', () => { 34 | beforeEach(visit); 35 | 36 | it('Bottom center notification should appear', () => { 37 | cy.get(notificationInput).clear().type(`${fakeText} bottom center`); 38 | createNotification(positionButtonBottomCenter); 39 | cy.contains(notificationBottomCenter, `${fakeText} bottom center`).should('to.exist'); 40 | }); 41 | 42 | it('Bottom right notification should appear', () => { 43 | cy.get(notificationInput).clear().type(`${fakeText} bottom right`); 44 | createNotification(positionButtonBottomRight); 45 | cy.contains(notificationBottomRight, `${fakeText} bottom right`).should('to.exist'); 46 | }); 47 | 48 | it('Bottom left notification should appear', () => { 49 | cy.get(notificationInput).clear().type(`${fakeText} bottom left`); 50 | createNotification(positionButtonBottomLeft); 51 | cy.contains(notificationBottomLeft, `${fakeText} bottom left`).should('to.exist'); 52 | }); 53 | 54 | it('Top left notification should appear', () => { 55 | cy.get(notificationInput).clear().type(`${fakeText} top left`); 56 | createNotification(positionButtonTopLeft); 57 | cy.contains(notificationTopLeft, `${fakeText} top left`).should('to.exist'); 58 | }); 59 | 60 | it('Top right notification should appear', () => { 61 | cy.get(notificationInput).clear().type(`${fakeText} top right`); 62 | createNotification(positionButtonTopRight); 63 | cy.contains(notificationTopRight, `${fakeText} top right`).should('to.exist'); 64 | }); 65 | 66 | it('Top center notification should appear', () => { 67 | cy.get(notificationInput).clear().type(`${fakeText} top center`); 68 | createNotification(positionButtonTopCenter); 69 | cy.contains(notificationTopCenter, `${fakeText} top center`).should('to.exist'); 70 | }); 71 | }); 72 | 73 | describe('Default notifications can be closed', () => { 74 | beforeEach(() => { 75 | visit(); 76 | cy.get(timerInput).clear().type('15000'); 77 | }); 78 | 79 | it('\'Clear all\' button clears all notifications', () => { 80 | cy.get(notificationInput).clear().type(`${fakeText} clear all`); 81 | cy.get(timerInput).clear().type('15000'); 82 | createNotification(positionButtonBottomCenter); 83 | createNotification(positionButtonBottomLeft); 84 | createNotification(positionButtonBottomRight); 85 | createNotification(positionButtonTopCenter); 86 | createNotification(positionButtonTopLeft); 87 | createNotification(positionButtonTopRight); 88 | cy.contains('button', 'Clear all').click(); 89 | cy.contains(notificationTopCenter, `${fakeText} clear all`).should('not.exist'); 90 | cy.contains(notificationTopLeft, `${fakeText} clear all`).should('not.exist'); 91 | cy.contains(notificationTopRight, `${fakeText} clear all`).should('not.exist'); 92 | cy.contains(notificationBottomCenter, `${fakeText} clear all`).should('not.exist'); 93 | cy.contains(notificationBottomLeft, `${fakeText} clear all`).should('not.exist'); 94 | cy.contains(notificationBottomRight, `${fakeText} clear all`).should('not.exist'); 95 | }); 96 | 97 | it('Can close bottom center notification', () => { 98 | cy.get(notificationInput).clear().type(`${fakeText} bottom center`); 99 | createNotification(positionButtonBottomCenter); 100 | cy.contains(notificationBottomCenter, `${fakeText} bottom center`).should('to.exist'); 101 | deleteNotification(); 102 | }); 103 | 104 | it('Can close bottom right notification', () => { 105 | cy.get(notificationInput).clear().type(`${fakeText} bottom right`); 106 | createNotification(positionButtonBottomRight); 107 | cy.contains(notificationBottomRight, `${fakeText} bottom right`).should('to.exist'); 108 | deleteNotification(); 109 | }); 110 | 111 | it('Can close bottom left notification should appear', () => { 112 | cy.get(notificationInput).clear().type(`${fakeText} bottom left`); 113 | createNotification(positionButtonBottomLeft); 114 | cy.contains(notificationBottomLeft, `${fakeText} bottom left`).should('to.exist'); 115 | deleteNotification(); 116 | }); 117 | 118 | it('Can close top left notification', () => { 119 | cy.get(notificationInput).clear().type(`${fakeText} top left`); 120 | createNotification(positionButtonTopLeft); 121 | cy.contains(notificationTopLeft, `${fakeText} top left`).should('to.exist'); 122 | deleteNotification(); 123 | }); 124 | 125 | it('Can close top right notification', () => { 126 | cy.get(notificationInput).clear().type(`${fakeText} top right`); 127 | createNotification(positionButtonTopRight); 128 | cy.contains(notificationTopRight, `${fakeText} top right`).should('to.exist'); 129 | deleteNotification(); 130 | }); 131 | 132 | it('Can close top center notification', () => { 133 | cy.get(notificationInput).clear().type(`${fakeText} top center`); 134 | createNotification(positionButtonTopCenter); 135 | cy.contains(notificationTopCenter, `${fakeText} top center`).should('to.exist'); 136 | deleteNotification(); 137 | }); 138 | }); 139 | 140 | describe('Default notifications are fading away after timeout', () => { 141 | // time during which component will remove after fade. 142 | // Svelte's default fade duration is ~400, adding 150ms to avoid inconsistencies 143 | const timeout = 50 + 400 + 150; 144 | 145 | beforeEach(() => { 146 | visit(); 147 | cy.get(timerInput).clear().type('50'); 148 | }); 149 | 150 | it('Bottom center notification fades away', () => { 151 | cy.get(notificationInput).clear().type(`${fakeText} bottom center`); 152 | createNotification(positionButtonBottomCenter); 153 | cy.contains(notificationBottomCenter, `${fakeText} bottom center`, { timeout }).should('not.exist'); 154 | }); 155 | 156 | it('Bottom right notification fades away', () => { 157 | cy.get(notificationInput).clear().type(`${fakeText} bottom right`); 158 | createNotification(positionButtonBottomRight); 159 | cy.contains(notificationBottomRight, `${fakeText} bottom right`, { timeout }).should('not.exist'); 160 | }); 161 | 162 | it('Bottom left notification fades away', () => { 163 | cy.get(notificationInput).clear().type(`${fakeText} bottom left`); 164 | createNotification(positionButtonBottomLeft); 165 | cy.contains(notificationBottomLeft, `${fakeText} bottom left`, { timeout }).should('not.exist'); 166 | }); 167 | 168 | it('Top left notification fades away', () => { 169 | cy.get(notificationInput).clear().type(`${fakeText} top left`); 170 | createNotification(positionButtonTopLeft); 171 | cy.contains(notificationTopLeft, `${fakeText} top left`, { timeout }).should('not.exist'); 172 | }); 173 | 174 | it('Top right notification fades away', () => { 175 | cy.get(notificationInput).clear().type(`${fakeText} top right`); 176 | createNotification(positionButtonTopRight); 177 | cy.contains(notificationTopRight, `${fakeText} top right`, { timeout }).should('not.exist'); 178 | }); 179 | 180 | it('Top center notification fades away', () => { 181 | cy.get(notificationInput).clear().type(`${fakeText} top center`); 182 | createNotification(positionButtonTopCenter); 183 | cy.contains(notificationTopCenter, `${fakeText} top center`, { timeout }).should('not.exist'); 184 | }); 185 | }); 186 | 187 | describe('Custom notifications are displayed correctly', () => { 188 | beforeEach(() => { 189 | cy.visit(Cypress.config().baseUrl); 190 | cy.get('.toggle-custom').click(); 191 | }); 192 | 193 | it('Bottom center custom notification should appear', () => { 194 | cy.get(notificationInput).clear().type(`${fakeText} custom bottom center`); 195 | createNotification(positionButtonBottomCenter); 196 | cy.contains(notificationBottomCenter, `${fakeText} custom bottom center`).should('to.exist'); 197 | cy.contains(`${notificationBottomCenter} p`, 'Custom description').should('to.exist'); 198 | }); 199 | }); 200 | 201 | describe('Custom notifications can be closed', () => { 202 | beforeEach(() => { 203 | cy.visit(Cypress.config().baseUrl); 204 | cy.get('.toggle-custom').click(); 205 | }); 206 | 207 | it('Bottom center custom notification can be closed', () => { 208 | cy.get(notificationInput).clear().type(`${fakeText} custom bottom center`); 209 | cy.get(timerInput).clear().type('500'); 210 | createNotification(positionButtonBottomCenter); 211 | cy.get(`${notificationBottomCenter} + .notification-buttons button`).last().click(); 212 | cy.contains(notificationBottomCenter, `${fakeText} custom bottom center`, { timeout: 1000 }).should('not.exist'); 213 | }); 214 | }); 215 | }); 216 | -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /example/App.svelte: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /example/Children.svelte: -------------------------------------------------------------------------------- 1 | 38 | 39 |
40 |
41 |

42 | Svelte notifications 43 | 48 | v0.9.97 49 | 50 |

51 |

Extremely simple and flexible notifications for Svelte

52 |
53 |
54 |
55 | 58 | 63 |
64 |
65 | 68 | 73 |
74 |
75 | 78 |
79 |
80 | 88 | 96 | 104 |
105 |
106 | 114 | 122 | 130 |
131 |
132 |
133 |
134 |
135 |
136 | 137 | 142 | 148 |
149 | {#if !showCustom} 150 |
151 | 152 | 157 | 163 |
164 | {/if} 165 |
166 | 180 | 186 |
187 | -------------------------------------------------------------------------------- /example/CustomItem.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 55 | 56 |
61 |
62 | {notification.text} 63 |

{notification.description || 'Custom description'}

64 |
65 |
66 | 69 | 72 |
73 |
74 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import App from './App.svelte'; 2 | 3 | export default new App({ 4 | target: document.body, 5 | }); 6 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import type { SvelteComponent } from 'svelte'; 2 | 3 | interface NotificationsProps { 4 | item?: SvelteComponent; 5 | withoutStyles?: boolean; 6 | zIndex?: string | number; 7 | } 8 | 9 | export default class Notifications extends SvelteComponent { 10 | $$prop_def: NotificationsProps; 11 | } 12 | 13 | export type Position = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'; 14 | 15 | interface DefaultNotificationOptions { 16 | id?: string; 17 | text?: string; 18 | position: Position; 19 | type?: string; 20 | removeAfter?: number; 21 | } 22 | 23 | type addNotification = (notification: DefaultNotificationOptions | Record) => void; 24 | 25 | type removeNotification = (notificationId: string) => void 26 | 27 | type clearNotifications = () => void; 28 | 29 | export function getNotificationsContext(): { 30 | subscribe: any; 31 | addNotification: addNotification; 32 | removeNotification: removeNotification; 33 | clearNotifications: clearNotifications; 34 | }; 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-notifications", 3 | "description": "Extremely simple and flexible notifications for Svelte", 4 | "version": "0.9.98", 5 | "type": "module", 6 | "svelte": "src/index.js", 7 | "main": "build/bundle.js", 8 | "module": "build/bundle.mjs", 9 | "types": "index.d.ts", 10 | "author": "Keenethics ", 11 | "license": "MIT", 12 | "keywords": [ 13 | "svelte", 14 | "notifications" 15 | ], 16 | "files": [ 17 | "src", 18 | "index.d.ts" 19 | ], 20 | "scripts": { 21 | "build": "rollup -c", 22 | "autobuild": "rollup -c -w", 23 | "dev": "run-p start:dev autobuild", 24 | "start": "sirv public", 25 | "start:dev": "sirv public --dev --port 5000", 26 | "lint": "eslint ./src --ext .js,.svelte", 27 | "cypress:open": "cypress open", 28 | "test:run": "cypress run", 29 | "ci": "start-server-and-test dev http://localhost:5000 test:run" 30 | }, 31 | "devDependencies": { 32 | "@rollup/plugin-commonjs": "^23.0.0", 33 | "@rollup/plugin-node-resolve": "^15.0.0", 34 | "autoprefixer": "^10.4.12", 35 | "cypress": "^10.10.0", 36 | "eslint": "^8.25.0", 37 | "eslint-config-airbnb-base": "^15.0.0", 38 | "eslint-plugin-cypress": "^2.12.1", 39 | "eslint-plugin-import": "^2.26.0", 40 | "eslint-plugin-svelte3": "^4.0.0", 41 | "npm-run-all": "^4.1.5", 42 | "postcss": "^8.4.18", 43 | "rollup": "^3.2.3", 44 | "rollup-plugin-css-only": "^3.1.0", 45 | "rollup-plugin-livereload": "^2.0.5", 46 | "rollup-plugin-svelte": "^7.1.0", 47 | "rollup-plugin-terser": "^7.0.2", 48 | "sirv-cli": "^2.0.2", 49 | "start-server-and-test": "^1.14.0", 50 | "svelte": "^3.52.0", 51 | "svelte-preprocess": "^4.10.7" 52 | }, 53 | "repository": { 54 | "type": "git", 55 | "url": "git://github.com/keenethics/svelte-notifications.git" 56 | }, 57 | "bugs": { 58 | "url": "https://github.com/keenethics/svelte-notifications/issues" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keenethics/svelte-notifications/0c1c2f70603d02a0e98935c88217bd9297c1e4ea/public/favicon.png -------------------------------------------------------------------------------- /public/global.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | font: inherit; 19 | vertical-align: baseline; 20 | } 21 | 22 | article, aside, details, figcaption, figure, 23 | footer, header, hgroup, menu, nav, section { 24 | display: block; 25 | } 26 | 27 | ol, ul { 28 | list-style: none; 29 | } 30 | 31 | blockquote, q { 32 | quotes: none; 33 | } 34 | 35 | blockquote:before, blockquote:after, 36 | q:before, q:after { 37 | content: ''; 38 | content: none; 39 | } 40 | 41 | table { 42 | border-collapse: collapse; 43 | border-spacing: 0; 44 | } 45 | 46 | body { 47 | font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; 48 | background: #2e2f40; 49 | color: rgba(255, 255, 255, 0.4); 50 | line-height: 1; 51 | } 52 | 53 | .row { 54 | margin: 0 -4px; 55 | } 56 | 57 | .row:before, .row:after { 58 | content: ""; 59 | display: table; 60 | } 61 | 62 | .row:after { 63 | clear: both; 64 | } 65 | 66 | .col { 67 | float: left; 68 | padding: 0 4px; 69 | box-sizing: border-box; 70 | } 71 | 72 | .col-1-2 { 73 | width: 50%; 74 | } 75 | 76 | .col-1-3 { 77 | width: 33.33%; 78 | } 79 | 80 | .col-1-4 { 81 | width: 25%; 82 | } 83 | 84 | .col-1-8 { 85 | width: 12.5%; 86 | } 87 | 88 | label { 89 | font-size: 16px; 90 | display: block; 91 | margin: 0 0 8px; 92 | color: rgba(255, 255, 255, 0.4); 93 | } 94 | 95 | input[type="text"] { 96 | font-size: 18px; 97 | background: #37425f; 98 | color: #fff; 99 | width: 100%; 100 | height: 40px; 101 | border-radius: 6px; 102 | border: 2px solid #4d6deb; 103 | padding: 0 6px; 104 | margin: 0 0 16px; 105 | outline: none; 106 | box-sizing: border-box; 107 | } 108 | 109 | button.button { 110 | font-size: 18px; 111 | background: #4d6deb; 112 | color: #fff; 113 | height: 40px; 114 | padding: 0 16px; 115 | border-radius: 6px; 116 | border: none; 117 | cursor: pointer; 118 | } 119 | 120 | button.button.secondary { 121 | background: #37425f; 122 | } 123 | 124 | input[type="text"]:focus, button.button:focus { 125 | box-shadow: 0 0 0 2px #37425f; 126 | } 127 | 128 | button.button:active { 129 | background: #434c66; 130 | } 131 | 132 | .example { 133 | max-width: 680px; 134 | margin: 100px auto 20px; 135 | padding: 0 10px; 136 | } 137 | 138 | .example-title { 139 | margin: 0 0 50px; 140 | } 141 | 142 | .example-title h1 { 143 | font-size: 24px; 144 | font-weight: 700; 145 | margin: 0 0 8px; 146 | color: #fff; 147 | } 148 | 149 | .example-title h1 a { 150 | vertical-align: super; 151 | font-size: 14px; 152 | font-weight: 400; 153 | color: #4d6deb; 154 | margin: 0 0 0 5px; 155 | text-decoration: none; 156 | } 157 | 158 | .example-title h1 a:hover { 159 | text-decoration: underline; 160 | } 161 | 162 | .example-title p { 163 | font-size: 14px; 164 | color: rgba(255, 255, 255, 0.4); 165 | } 166 | 167 | .position-select { 168 | overflow: hidden; 169 | background: #37425f; 170 | color: #fff; 171 | width: 100%; 172 | height: 40px; 173 | margin: 0 0 16px; 174 | padding: 0; 175 | border-radius: 6px; 176 | border: 2px solid #4d6deb; 177 | overflow: hidden; 178 | box-sizing: border-box; 179 | outline: none; 180 | } 181 | 182 | .position-select-row { 183 | border-bottom: 1px solid #4d6deb; 184 | height: 50%; 185 | } 186 | 187 | .position-select-row:last-child { 188 | border-bottom: none; 189 | } 190 | 191 | .position-select button { 192 | border: none; 193 | border-left: 1px solid #4d6deb; 194 | border-right: 1px solid #4d6deb; 195 | background: none; 196 | color: #fff; 197 | width: 33.33%; 198 | height: 100%; 199 | float: left; 200 | cursor: pointer; 201 | } 202 | 203 | .position-select button:first-child, 204 | .position-select button:last-child { 205 | border-left: none; 206 | border-right: none; 207 | } 208 | 209 | .position-select button:hover { 210 | background: #434c66; 211 | } 212 | 213 | .position-select button.active { 214 | background: #4d6deb; 215 | } 216 | 217 | @media all and (max-width: 680px) { 218 | .row { 219 | margin: 0; 220 | } 221 | 222 | .col-1-3 { 223 | width: 100%; 224 | padding: 0; 225 | } 226 | 227 | .col-1-4 { 228 | width: 100%; 229 | padding: 0; 230 | } 231 | 232 | .position-select { 233 | height: 60px; 234 | } 235 | } 236 | 237 | .show-custom { 238 | display: flex; 239 | align-items: center; 240 | padding-bottom: 10px; 241 | } 242 | 243 | input[type="checkbox"] { 244 | display: none; 245 | } 246 | 247 | label.label-show-custom { 248 | margin-bottom: 0; 249 | transition: color .3s ease; 250 | font-size: 14px; 251 | 252 | cursor: pointer; 253 | } 254 | 255 | .toggle { 256 | margin-right: 10px; 257 | display: block; 258 | position: relative; 259 | width: 40px; 260 | height: 22px; 261 | border: 1px solid #4d6deb; 262 | border-radius: 15px; 263 | background-color: #37425f; 264 | 265 | cursor: pointer; 266 | } 267 | 268 | .toggle:after { 269 | content: ''; 270 | position: absolute; 271 | left: 0; 272 | top: 0; 273 | width: 16px; 274 | height: 16px; 275 | margin: 3px; 276 | border-radius: 50%; 277 | background-color: #4d6deb; 278 | transition: left .2s ease; 279 | } 280 | 281 | input[type="checkbox"]:checked + .toggle:after { 282 | left: calc(100% - 22px); 283 | } 284 | 285 | .label-show-custom.active { 286 | color: #fff; 287 | } 288 | 289 | .additional-tools { 290 | overflow: hidden; 291 | padding: 10px 0 0; 292 | margin: 0 0 15px; 293 | border-top: 1px solid rgba(255, 255, 255, 0.1); 294 | border-bottom: 1px solid rgba(255, 255, 255, 0.1); 295 | } 296 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte Notifications 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import autoprefixer from 'autoprefixer'; 2 | 3 | import svelte from 'rollup-plugin-svelte'; 4 | import resolve from '@rollup/plugin-node-resolve'; 5 | import commonjs from '@rollup/plugin-commonjs'; 6 | import livereload from 'rollup-plugin-livereload'; 7 | import { terser } from 'rollup-plugin-terser'; 8 | import css from 'rollup-plugin-css-only'; 9 | 10 | import preprocess from 'svelte-preprocess'; 11 | 12 | const preprocessOptions = { 13 | postcss: { 14 | plugins: [ 15 | autoprefixer, 16 | ], 17 | }, 18 | }; 19 | const production = !process.env.ROLLUP_WATCH; 20 | 21 | const config = production 22 | ? ({ 23 | input: 'src/index.js', 24 | output: [ 25 | { 26 | file: 'build/bundle.mjs', 27 | format: 'es', 28 | }, 29 | { 30 | file: 'build/bundle.js', 31 | format: 'umd', 32 | exports: 'named', 33 | name: 'Notifications', 34 | }, 35 | ], 36 | plugins: [ 37 | css({ 38 | output: 'bundle.css', 39 | }), 40 | svelte({ 41 | preprocess: preprocess(preprocessOptions), 42 | compilerOptions: { 43 | dev: !production, 44 | }, 45 | }), 46 | resolve({ 47 | browser: true, 48 | dedupe: ['svelte'], 49 | }), 50 | terser(), 51 | ], 52 | }) 53 | : ({ 54 | input: 'example/index.js', 55 | output: { 56 | sourcemap: true, 57 | format: 'iife', 58 | name: 'app', 59 | file: 'public/bundle.js', 60 | }, 61 | plugins: [ 62 | css({ 63 | output: 'bundle.css', 64 | }), 65 | svelte({ 66 | preprocess: preprocess(preprocessOptions), 67 | compilerOptions: { 68 | dev: !production, 69 | }, 70 | }), 71 | resolve(), 72 | commonjs(), 73 | livereload('public'), 74 | ], 75 | }); 76 | 77 | export default config; 78 | -------------------------------------------------------------------------------- /src/components/DefaultNotification.svelte: -------------------------------------------------------------------------------- 1 | 73 | 74 | 94 | 95 | {#if text} 96 |
103 |
104 | {text} 105 |
106 | 113 |
114 | {/if} 115 | -------------------------------------------------------------------------------- /src/components/Notification.svelte: -------------------------------------------------------------------------------- 1 | 28 | 29 | 35 | -------------------------------------------------------------------------------- /src/components/Notifications.svelte: -------------------------------------------------------------------------------- 1 | 44 | 66 | 67 | 68 |
69 | {#each positions as position} 70 |
74 | {#each $store as notification (notification.id)} 75 | {#if notification.position === position} 76 | 81 | {/if} 82 | {/each} 83 |
84 | {/each} 85 |
86 | -------------------------------------------------------------------------------- /src/context.js: -------------------------------------------------------------------------------- 1 | import { getContext } from 'svelte'; 2 | 3 | const context = { 4 | subscribe: null, 5 | addNotification: null, 6 | removeNotification: null, 7 | clearNotifications: null, 8 | }; 9 | 10 | export const getNotificationsContext = () => getContext(context); 11 | 12 | export default context; 13 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Notifications from './components/Notifications.svelte'; 2 | 3 | export { getNotificationsContext } from './context'; 4 | 5 | export default Notifications; 6 | -------------------------------------------------------------------------------- /src/positions.js: -------------------------------------------------------------------------------- 1 | const positions = [ 2 | 'top-left', 3 | 'top-center', 4 | 'top-right', 5 | 'bottom-left', 6 | 'bottom-center', 7 | 'bottom-right', 8 | ]; 9 | 10 | export default positions; 11 | -------------------------------------------------------------------------------- /src/store/actions/addNotification.js: -------------------------------------------------------------------------------- 1 | import positions from '../../positions'; 2 | 3 | const addNotification = (notification, store) => { 4 | if (!notification) return; 5 | 6 | const { update } = store; 7 | const safeNotification = { 8 | id: `${new Date().getTime()}-${Math.floor(Math.random() * 9999)}`, 9 | position: 'bottom-center', 10 | text: '', 11 | ...notification, 12 | }; 13 | 14 | if (!positions.includes(notification.position)) return; 15 | 16 | update((notifications) => { 17 | if (safeNotification.position.includes('top-')) { 18 | return [safeNotification, ...notifications]; 19 | } 20 | 21 | return [...notifications, safeNotification]; 22 | }); 23 | }; 24 | 25 | export default addNotification; 26 | -------------------------------------------------------------------------------- /src/store/actions/clearNotifications.js: -------------------------------------------------------------------------------- 1 | const clearNotifications = (store) => store.set([]); 2 | 3 | export default clearNotifications; 4 | -------------------------------------------------------------------------------- /src/store/actions/removeNotification.js: -------------------------------------------------------------------------------- 1 | const removeNotification = (notificationId, { update }) => { 2 | if (!notificationId) return; 3 | 4 | update((notifications) => notifications.filter(({ id }) => id !== notificationId)); 5 | }; 6 | 7 | export default removeNotification; 8 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | import addNotification from './actions/addNotification'; 4 | import removeNotification from './actions/removeNotification'; 5 | import clearNotification from './actions/clearNotifications'; 6 | 7 | const createStore = () => { 8 | const store = writable([]); 9 | 10 | return { 11 | subscribe: store.subscribe, 12 | addNotification: (notification) => addNotification(notification, store), 13 | removeNotification: (notificationId) => removeNotification(notificationId, store), 14 | clearNotifications: () => clearNotification(store), 15 | }; 16 | }; 17 | 18 | export default createStore(); 19 | --------------------------------------------------------------------------------