notifications-testi 1.0.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 +21 -0
- package/README.md +125 -0
- package/index.d.ts +29 -0
- package/notifications-kd.js +542 -0
- package/notifications-kd.min.js +1 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 KhvichaDev
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# KD Notifications (notifications-kd)
|
|
2
|
+
|
|
3
|
+
A highly optimized, ultra-lightweight toast notification system with a modern, elegant UI, fully responsive mobile support, and zero dependencies.
|
|
4
|
+
|
|
5
|
+
[](https://KhvichaDev.github.io/notifications-kd/) [](https://github.com/KhvichaDev/notifications-kd/releases) [](https://github.com/KhvichaDev/notifications-kd/issues)
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
**Via NPM (Bundlers):**
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install notifications-kd
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Via CDN (Direct Browser Usage):**
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<script src="https://cdn.jsdelivr.net/npm/notifications-kd"></script>
|
|
19
|
+
|
|
20
|
+
<script src="https://unpkg.com/notifications-kd"></script>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- ✨ **Elegant & Modern UI**: Beautifully crafted default themes that instantly upgrade your application's look and feel.
|
|
26
|
+
- 🚀 **Zero Dependencies**: Pure Vanilla JS. No jQuery, no external CSS.
|
|
27
|
+
- 🛡️ **Secure**: Built-in DOM-based XSS protection (strict whitelisting & sanitization).
|
|
28
|
+
- 🎨 **Auto-Theming**: Automatically detects host environment (Light/Dark mode) and adapts seamlessly.
|
|
29
|
+
- 📍 **Smart Positioning**: Place toasts anywhere (center, top-left, bottom-right, etc.) with custom offsets.
|
|
30
|
+
- ♿ **Accessible**: Full keyboard navigation (Tab-trapping) for modal states.
|
|
31
|
+
- 📱 **Responsive & Mobile-Ready**: Fully adaptive layout that works flawlessly on phones, tablets, and desktops.
|
|
32
|
+
- ⚡ **High Performance**: Optimized DOM manipulation, `O(1)` lookups, and strict memory management.
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
Include the library in your project:
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
import KDNotification from "notifications-kd";
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
_(Or simply load the `notifications-kd.js` file via a `<script>` tag in browser environments)._
|
|
43
|
+
|
|
44
|
+
### Basic Info Toast
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
KDNotification.show({
|
|
48
|
+
type: "info",
|
|
49
|
+
message: "Operation completed successfully.",
|
|
50
|
+
position: "top-right",
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Rich Toast with Title & Auto-dismiss
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
KDNotification.show({
|
|
58
|
+
type: "success",
|
|
59
|
+
title: "Profile Updated",
|
|
60
|
+
message: "Your changes have been saved.",
|
|
61
|
+
duration: 4000,
|
|
62
|
+
position: "bottom-left",
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Interactive Modal (Promises)
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
KDNotification.show({
|
|
70
|
+
type: "warning",
|
|
71
|
+
title: "Delete File?",
|
|
72
|
+
message: "Are you sure you want to permanently delete this file?",
|
|
73
|
+
isModal: true, // Blocks background clicks and escapes
|
|
74
|
+
position: "center",
|
|
75
|
+
buttons: [
|
|
76
|
+
{ text: "Yes, Delete", className: "kd-btn-danger", value: "deleted" },
|
|
77
|
+
{ text: "Cancel", value: "cancelled" },
|
|
78
|
+
],
|
|
79
|
+
}).then((res) => {
|
|
80
|
+
if (res === "deleted") {
|
|
81
|
+
console.log("File deleted!");
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## API Options
|
|
87
|
+
|
|
88
|
+
| Parameter | Type | Default | Description |
|
|
89
|
+
| ----------- | ------------- | ---------- | -------------------------------------------------------------------------- |
|
|
90
|
+
| `type` | String | `'info'` | `'info'`, `'success'`, `'error'`, `'warning'`, `'processing'` |
|
|
91
|
+
| `title` | String | `''` | Optional title for a rich UI layout |
|
|
92
|
+
| `message` | String | `''` | The main content of the notification (Safe HTML supported) |
|
|
93
|
+
| `duration` | Number | `3000` | Auto-dismiss time in ms (`0` disables auto-dismiss) |
|
|
94
|
+
| `position` | String | `'center'` | `'center'`, `'top-left'`, `'top-right'`, `'bottom-left'`, `'bottom-right'` |
|
|
95
|
+
| `theme` | String | `'auto'` | `'auto'` (detects background), `'light'`, or `'dark'` |
|
|
96
|
+
| `isModal` | Boolean | `false` | If true, forces user interaction (disables outside click/Esc) |
|
|
97
|
+
| `buttons` | Array | `null` | Array of button objects `{ text, value, className, onClick }` |
|
|
98
|
+
| `icon` | String | `null` | Custom SVG string to override the default icon |
|
|
99
|
+
| `style` | Object | `{}` | Custom inline CSS properties or CSS variables |
|
|
100
|
+
| `offsetX/Y` | String/Number | `'50px'` | Custom distance from screen edges for corner positions |
|
|
101
|
+
|
|
102
|
+
## Methods
|
|
103
|
+
|
|
104
|
+
- **`KDNotification.show(options)`**: Displays the notification and returns a `Promise`. Resolves with the clicked button's `value`, or `null`/`true` on auto-dismiss or overlay click.
|
|
105
|
+
- **`KDNotification.close()`**: Programmatically dismisses the currently active notification. Ideal for hiding `processing` toasts after a background task (like an API call) finishes.
|
|
106
|
+
|
|
107
|
+
## Customization & CSS
|
|
108
|
+
|
|
109
|
+
You can globally override default styles via the `:root` pseudo-class, or locally per toast using the `style` option API:
|
|
110
|
+
|
|
111
|
+
- `--kd-toast-anim-duration`: Controls the enter/leave animation speed (Default: `250ms`).
|
|
112
|
+
- `--kd-z-toast`: Controls the z-index of the toast overlay (Default: `2147483647`).
|
|
113
|
+
- `--kd-mobile-offset-x`: Controls the horizontal distance from screen edges on mobile devices (Default: `12px`).
|
|
114
|
+
- `--kd-mobile-offset-y`: Controls the vertical distance from screen edges on mobile devices (Default: `16px`).
|
|
115
|
+
- `--kd-mobile-max-width`: Controls the maximum width of the toast on mobile devices (Default: `420px`).
|
|
116
|
+
|
|
117
|
+
> 💡 **Best Practice:** It is strongly recommended to use the `style` API or CSS variables for customization. Please avoid targeting internal CSS classes directly in your stylesheet, as the internal HTML structure may evolve in future minor updates and could break your custom overrides.
|
|
118
|
+
|
|
119
|
+
## Contact & Support
|
|
120
|
+
|
|
121
|
+
If you have any questions, bug reports, or feature requests, please [open an issue](https://github.com/KhvichaDev/notifications-kd/issues) on the GitHub repository.
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MIT License © KhvichaDev
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface NotificationButton {
|
|
2
|
+
text: string;
|
|
3
|
+
value?: any;
|
|
4
|
+
className?: string;
|
|
5
|
+
onClick?: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface NotificationOptions {
|
|
9
|
+
type?: 'info' | 'success' | 'error' | 'warning' | 'processing';
|
|
10
|
+
title?: string;
|
|
11
|
+
message?: string;
|
|
12
|
+
duration?: number;
|
|
13
|
+
buttons?: NotificationButton[];
|
|
14
|
+
isModal?: boolean;
|
|
15
|
+
icon?: string;
|
|
16
|
+
style?: Record<string, string | number>;
|
|
17
|
+
theme?: 'auto' | 'light' | 'dark';
|
|
18
|
+
position?: 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
19
|
+
offsetX?: string | number;
|
|
20
|
+
offsetY?: string | number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface KDNotificationAPI {
|
|
24
|
+
show(options: NotificationOptions): Promise<any>;
|
|
25
|
+
close(): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
declare const KDNotification: KDNotificationAPI;
|
|
29
|
+
export default KDNotification;
|
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notifications KD Library
|
|
3
|
+
* Author: KhvichaDev
|
|
4
|
+
* A standalone, plug-and-play toast notification system.
|
|
5
|
+
*/
|
|
6
|
+
(function (global, factory) {
|
|
7
|
+
/** Force global assignment to ensure availability in environments with conflicting AMD loaders */
|
|
8
|
+
const lib = factory();
|
|
9
|
+
global.KDNotification = lib;
|
|
10
|
+
|
|
11
|
+
if (typeof module === "object" && typeof module.exports === "object") {
|
|
12
|
+
module.exports = lib;
|
|
13
|
+
} else if (typeof define === "function" && define.amd) {
|
|
14
|
+
define([], function() { return lib; });
|
|
15
|
+
}
|
|
16
|
+
}(typeof globalThis !== "undefined" ? globalThis : (typeof window !== "undefined" ? window : this), function () {
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
let activeCloser = null;
|
|
20
|
+
let notificationTimeout = null;
|
|
21
|
+
let animationTimeout = null;
|
|
22
|
+
|
|
23
|
+
/** Lazily injects the library's complete stylesheet into the document head on first use,
|
|
24
|
+
* making the library fully self-contained with zero external CSS dependencies.
|
|
25
|
+
*/
|
|
26
|
+
function injectCSS() {
|
|
27
|
+
if (document.getElementById('kd-notification-styles')) return;
|
|
28
|
+
|
|
29
|
+
const css = `
|
|
30
|
+
:root, .kd-theme-dark {
|
|
31
|
+
--kd-accent-color: #4da6ff;
|
|
32
|
+
--kd-accent-bg-transparent: rgba(77, 166, 255, 0.15);
|
|
33
|
+
--kd-z-toast: 2147483647;
|
|
34
|
+
--kd-bg-surface-elevated: #2c2c2c;
|
|
35
|
+
--kd-bg-surface-toast: #2b2b2b;
|
|
36
|
+
--kd-text-primary: #e3e3e3;
|
|
37
|
+
--kd-text-secondary: #c4c7c5;
|
|
38
|
+
--kd-border-subtle: rgba(255, 255, 255, 0.1);
|
|
39
|
+
--kd-btn-bg: rgba(255, 255, 255, 0.08);
|
|
40
|
+
--kd-color-success: #81c995;
|
|
41
|
+
--kd-bg-success-transparent: rgba(46, 204, 113, 0.15);
|
|
42
|
+
--kd-color-error: #f4877d;
|
|
43
|
+
--kd-color-warning: #f1c40f;
|
|
44
|
+
--kd-gradient-primary: linear-gradient(135deg, #a8c7fa, #8ab4f8);
|
|
45
|
+
--kd-gradient-primary-hover: linear-gradient(135deg, #b8d4fc, #9bbdfc);
|
|
46
|
+
--kd-gradient-danger: linear-gradient(135deg, #f4877d, #e74c3c);
|
|
47
|
+
--kd-gradient-danger-hover: linear-gradient(135deg, #f6a199, #ec7063);
|
|
48
|
+
--kd-toast-anim-duration: 250ms;
|
|
49
|
+
--kd-font-family: system-ui, -apple-system, sans-serif;
|
|
50
|
+
--kd-shadow-toast: 0 14px 40px rgba(0, 0, 0, 0.6);
|
|
51
|
+
--kd-mobile-offset-x: 12px;
|
|
52
|
+
--kd-mobile-offset-y: 16px;
|
|
53
|
+
--kd-mobile-max-width: 420px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.kd-theme-light {
|
|
57
|
+
--kd-bg-surface-elevated: #f5f5f5;
|
|
58
|
+
--kd-bg-surface-toast: #ffffff;
|
|
59
|
+
--kd-text-primary: #1c1c1e;
|
|
60
|
+
--kd-text-secondary: #5a5a5e;
|
|
61
|
+
--kd-border-subtle: rgba(0, 0, 0, 0.1);
|
|
62
|
+
--kd-btn-bg: rgba(0, 0, 0, 0.05);
|
|
63
|
+
--kd-accent-color: #007bff;
|
|
64
|
+
--kd-accent-bg-transparent: rgba(0, 123, 255, 0.15);
|
|
65
|
+
--kd-shadow-toast: 0 14px 40px rgba(0, 0, 0, 0.15);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.kd-toast-overlay {
|
|
69
|
+
position: fixed;
|
|
70
|
+
top: 0; left: 0;
|
|
71
|
+
width: 100%; height: 100%;
|
|
72
|
+
z-index: var(--kd-z-toast);
|
|
73
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
74
|
+
display: flex;
|
|
75
|
+
opacity: 0;
|
|
76
|
+
will-change: opacity;
|
|
77
|
+
transition: opacity var(--kd-toast-anim-duration) ease-in-out;
|
|
78
|
+
pointer-events: auto;
|
|
79
|
+
font-family: var(--kd-font-family);
|
|
80
|
+
box-sizing: border-box;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.kd-toast-overlay.kd-pos-center { align-items: center; justify-content: center; padding: 0; }
|
|
84
|
+
.kd-toast-overlay.kd-pos-top-left { align-items: flex-start; justify-content: flex-start; padding: var(--kd-toast-offset-y, 50px) var(--kd-toast-offset-x, 50px); }
|
|
85
|
+
.kd-toast-overlay.kd-pos-top-right { align-items: flex-start; justify-content: flex-end; padding: var(--kd-toast-offset-y, 50px) var(--kd-toast-offset-x, 50px); }
|
|
86
|
+
.kd-toast-overlay.kd-pos-bottom-left { align-items: flex-end; justify-content: flex-start; padding: var(--kd-toast-offset-y, 50px) var(--kd-toast-offset-x, 50px); }
|
|
87
|
+
.kd-toast-overlay.kd-pos-bottom-right { align-items: flex-end; justify-content: flex-end; padding: var(--kd-toast-offset-y, 50px) var(--kd-toast-offset-x, 50px); }
|
|
88
|
+
|
|
89
|
+
.kd-toast-overlay.visible { opacity: 1; }
|
|
90
|
+
|
|
91
|
+
.kd-toast {
|
|
92
|
+
pointer-events: auto;
|
|
93
|
+
transform: scale(0.9);
|
|
94
|
+
will-change: transform;
|
|
95
|
+
background-color: var(--kd-bg-surface-toast);
|
|
96
|
+
color: var(--kd-text-primary);
|
|
97
|
+
padding: 28px;
|
|
98
|
+
border-radius: 12px;
|
|
99
|
+
border: 1px solid var(--kd-border-subtle);
|
|
100
|
+
border-left: 3px solid transparent;
|
|
101
|
+
box-shadow: var(--kd-shadow-toast);
|
|
102
|
+
transition: transform var(--kd-toast-anim-duration) ease-in-out;
|
|
103
|
+
min-width: 340px; max-width: 500px; min-height: 160px;
|
|
104
|
+
display: flex; flex-direction: column; justify-content: center;
|
|
105
|
+
box-sizing: border-box; overflow: hidden;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.kd-toast-overlay.visible .kd-toast { transform: scale(1); }
|
|
109
|
+
|
|
110
|
+
.kd-toast-content {
|
|
111
|
+
display: flex; flex-direction: column; align-items: center; gap: 12px;
|
|
112
|
+
font-size: 15px; text-align: center;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.kd-toast-actions {
|
|
116
|
+
display: flex; justify-content: center; gap: 16px; margin-top: 20px; width: 100%;
|
|
117
|
+
}
|
|
118
|
+
.kd-toast-actions:empty { display: none; }
|
|
119
|
+
|
|
120
|
+
.kd-toast-actions button {
|
|
121
|
+
font-size: 14px; padding: 10px 24px; border-radius: 24px; border: none;
|
|
122
|
+
cursor: pointer; font-weight: 600; letter-spacing: 0.5px;
|
|
123
|
+
font-family: inherit;
|
|
124
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
|
|
125
|
+
background-color: var(--kd-btn-bg); color: var(--kd-text-primary);
|
|
126
|
+
border: 1px solid var(--kd-border-subtle);
|
|
127
|
+
transition: transform 0.2s, box-shadow 0.2s, background-color 0.2s;
|
|
128
|
+
}
|
|
129
|
+
.kd-toast-actions button:hover {
|
|
130
|
+
transform: translateY(-2px); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
|
|
131
|
+
background-color: var(--kd-bg-surface-elevated);
|
|
132
|
+
}
|
|
133
|
+
.kd-toast-actions button:active { transform: translateY(0); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); }
|
|
134
|
+
|
|
135
|
+
.kd-toast-actions button:focus-visible {
|
|
136
|
+
outline: 2px solid var(--kd-accent-color); outline-offset: 2px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.kd-toast-actions .kd-btn-primary { background: var(--kd-gradient-primary); color: #000; }
|
|
140
|
+
.kd-toast-actions .kd-btn-primary:hover { background: var(--kd-gradient-primary-hover); }
|
|
141
|
+
|
|
142
|
+
.kd-toast-actions .kd-btn-danger { background: var(--kd-gradient-danger); color: #fff; }
|
|
143
|
+
.kd-toast-actions .kd-btn-danger:hover { background: var(--kd-gradient-danger-hover); }
|
|
144
|
+
|
|
145
|
+
.kd-toast-text { color: var(--kd-text-primary); line-height: 1.7em; letter-spacing: 0.5px; }
|
|
146
|
+
.kd-toast:not(:has(.kd-toast-rich-container)) .kd-toast-text { text-align: center; }
|
|
147
|
+
|
|
148
|
+
.kd-toast-rich-container { display: flex; align-items: flex-start; gap: 16px; width: 100%; text-align: left; }
|
|
149
|
+
.kd-toast-rich-icon {
|
|
150
|
+
flex-shrink: 0; width: 42px; height: 42px; display: flex; align-items: center; justify-content: center;
|
|
151
|
+
border-radius: 50%; background-color: rgba(255, 255, 255, 0.1);
|
|
152
|
+
}
|
|
153
|
+
.kd-toast-rich-icon svg { width: 24px; height: 24px; fill: currentColor; }
|
|
154
|
+
.kd-toast-rich-content { display: flex; flex-direction: column; justify-content: center; gap: 4px; flex-grow: 1; }
|
|
155
|
+
.kd-toast-rich-title { font-weight: 700; font-size: 16px; color: var(--kd-text-primary); line-height: 1.2; }
|
|
156
|
+
.kd-toast-rich-text { font-size: 14px; color: var(--kd-text-secondary); line-height: 1.4; }
|
|
157
|
+
|
|
158
|
+
.kd-toast-custom-icon {
|
|
159
|
+
display: none; width: 44px; height: 44px; border-radius: 50%; margin-bottom: 12px;
|
|
160
|
+
align-self: center; margin-left: auto; margin-right: auto;
|
|
161
|
+
justify-content: center; align-items: center;
|
|
162
|
+
background: rgba(137, 180, 250, 0.1); color: var(--kd-accent-color); flex-shrink: 0;
|
|
163
|
+
}
|
|
164
|
+
.kd-toast-custom-icon svg { width: 24px; height: 24px; fill: currentColor; }
|
|
165
|
+
|
|
166
|
+
.kd-toast.kd-type-error { border-left-color: var(--kd-color-error); box-shadow: var(--kd-shadow-toast), 0 0 20px rgba(244, 135, 125, 0.1); }
|
|
167
|
+
.kd-toast.kd-type-error .kd-toast-rich-icon, .kd-toast.kd-type-error .kd-toast-custom-icon { color: var(--kd-color-error); background-color: rgba(244, 135, 125, 0.15); }
|
|
168
|
+
|
|
169
|
+
.kd-toast.kd-type-warning { border-left-color: var(--kd-color-warning); box-shadow: var(--kd-shadow-toast), 0 0 20px rgba(241, 196, 15, 0.1); }
|
|
170
|
+
.kd-toast.kd-type-warning .kd-toast-rich-icon, .kd-toast.kd-type-warning .kd-toast-custom-icon { color: var(--kd-color-warning); background-color: rgba(241, 196, 15, 0.15); }
|
|
171
|
+
|
|
172
|
+
.kd-toast.kd-type-success { border-left-color: var(--kd-color-success); box-shadow: var(--kd-shadow-toast), 0 0 20px rgba(46, 204, 113, 0.1); }
|
|
173
|
+
.kd-toast.kd-type-success .kd-toast-rich-icon, .kd-toast.kd-type-success .kd-toast-custom-icon { color: var(--kd-color-success); background-color: var(--kd-bg-success-transparent); }
|
|
174
|
+
|
|
175
|
+
.kd-toast.kd-type-info, .kd-toast.kd-type-processing { border-left-color: var(--kd-accent-color); box-shadow: var(--kd-shadow-toast), 0 0 20px rgba(77, 166, 255, 0.1); }
|
|
176
|
+
.kd-toast.kd-type-info .kd-toast-rich-icon, .kd-toast.kd-type-info .kd-toast-custom-icon, .kd-toast.kd-type-processing .kd-toast-custom-icon { color: var(--kd-accent-color); background-color: var(--kd-accent-bg-transparent); }
|
|
177
|
+
|
|
178
|
+
.kd-toast.kd-type-info:has(.kd-toast-actions button) {
|
|
179
|
+
background-color: var(--kd-bg-surface-elevated); padding: 28px 32px; border-radius: 24px; max-width: 500px; text-align: left;
|
|
180
|
+
}
|
|
181
|
+
.kd-toast.kd-type-info:has(.kd-toast-actions button) .kd-toast-actions { justify-content: flex-end; margin-top: 24px; }
|
|
182
|
+
|
|
183
|
+
@keyframes kd-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
|
184
|
+
.kd-anm-rotate { animation: kd-spin 1s linear infinite; transform-origin: center; }
|
|
185
|
+
|
|
186
|
+
@media (max-width: 600px) {
|
|
187
|
+
.kd-toast-overlay {
|
|
188
|
+
--kd-toast-offset-x: var(--kd-mobile-offset-x) !important;
|
|
189
|
+
--kd-toast-offset-y: var(--kd-mobile-offset-y) !important;
|
|
190
|
+
}
|
|
191
|
+
.kd-toast-overlay.kd-pos-center {
|
|
192
|
+
padding: var(--kd-toast-offset-y) var(--kd-toast-offset-x);
|
|
193
|
+
}
|
|
194
|
+
.kd-toast-overlay.kd-pos-top-left,
|
|
195
|
+
.kd-toast-overlay.kd-pos-top-right {
|
|
196
|
+
justify-content: center;
|
|
197
|
+
}
|
|
198
|
+
.kd-toast-overlay.kd-pos-bottom-left,
|
|
199
|
+
.kd-toast-overlay.kd-pos-bottom-right {
|
|
200
|
+
justify-content: center;
|
|
201
|
+
}
|
|
202
|
+
.kd-toast {
|
|
203
|
+
min-width: 0;
|
|
204
|
+
width: 100%;
|
|
205
|
+
max-width: var(--kd-mobile-max-width);
|
|
206
|
+
padding: 24px 20px;
|
|
207
|
+
}
|
|
208
|
+
.kd-toast.kd-type-info:has(.kd-toast-actions button) {
|
|
209
|
+
padding: 24px 20px;
|
|
210
|
+
max-width: var(--kd-mobile-max-width);
|
|
211
|
+
}
|
|
212
|
+
.kd-toast-actions {
|
|
213
|
+
flex-direction: column;
|
|
214
|
+
gap: 12px;
|
|
215
|
+
}
|
|
216
|
+
.kd-toast-actions button {
|
|
217
|
+
width: 100%;
|
|
218
|
+
padding: 14px 24px;
|
|
219
|
+
}
|
|
220
|
+
.kd-toast-rich-icon { width: 48px; height: 48px; }
|
|
221
|
+
.kd-toast-rich-icon svg { width: 26px; height: 26px; }
|
|
222
|
+
.kd-toast-custom-icon { width: 50px; height: 50px; margin-bottom: 16px; }
|
|
223
|
+
.kd-toast-custom-icon svg { width: 28px; height: 28px; }
|
|
224
|
+
.kd-toast-rich-title { font-size: 18px; }
|
|
225
|
+
.kd-toast-rich-text { font-size: 15px; }
|
|
226
|
+
.kd-toast-text { font-size: 16px; }
|
|
227
|
+
}
|
|
228
|
+
`;
|
|
229
|
+
|
|
230
|
+
const style = document.createElement('style');
|
|
231
|
+
style.id = 'kd-notification-styles';
|
|
232
|
+
style.textContent = css;
|
|
233
|
+
document.head.appendChild(style);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Analyzes the host page's background color and font to automatically match the notification
|
|
237
|
+
* theme. Falls back to the system's prefers-color-scheme when the background is transparent.
|
|
238
|
+
*/
|
|
239
|
+
function detectEnvironment() {
|
|
240
|
+
const bodyStyle = window.getComputedStyle(document.body);
|
|
241
|
+
const bgColor = bodyStyle.backgroundColor;
|
|
242
|
+
const font = bodyStyle.fontFamily;
|
|
243
|
+
|
|
244
|
+
let isLight = false;
|
|
245
|
+
const rgbMatch = bgColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
246
|
+
|
|
247
|
+
if (rgbMatch) {
|
|
248
|
+
const r = parseInt(rgbMatch[1], 10);
|
|
249
|
+
const g = parseInt(rgbMatch[2], 10);
|
|
250
|
+
const b = parseInt(rgbMatch[3], 10);
|
|
251
|
+
const aMatch = bgColor.match(/rgba?\(\d+,\s*\d+,\s*\d+,\s*([\d.]+)/);
|
|
252
|
+
const alpha = aMatch ? parseFloat(aMatch[1]) : 1;
|
|
253
|
+
|
|
254
|
+
if (alpha === 0 || bgColor === 'transparent') {
|
|
255
|
+
isLight = !window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
256
|
+
} else {
|
|
257
|
+
/** Calculates perceived brightness using standard relative luminance formula to determine text contrast */
|
|
258
|
+
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
259
|
+
isLight = luminance > 0.5;
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
isLight = !window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return { isLight, font };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const DANGEROUS_TAGS = new Set(['script', 'object', 'embed', 'iframe', 'frameset', 'form', 'base', 'meta', 'link', 'style']);
|
|
269
|
+
const ALLOWED_TYPES = new Set(['info', 'success', 'error', 'warning', 'processing']);
|
|
270
|
+
const ALLOWED_POSITIONS = new Set(['center', 'top-left', 'top-right', 'bottom-left', 'bottom-right']);
|
|
271
|
+
|
|
272
|
+
let sharedDOMParser = null;
|
|
273
|
+
|
|
274
|
+
/** Prevents Cross-Site Scripting attacks by parsing raw HTML strings into a DOM tree
|
|
275
|
+
* and stripping out dangerous tags and malicious attribute values before rendering.
|
|
276
|
+
*/
|
|
277
|
+
function safeFormatMessage(html) {
|
|
278
|
+
if (typeof html !== 'string') return '';
|
|
279
|
+
|
|
280
|
+
if (!sharedDOMParser) {
|
|
281
|
+
sharedDOMParser = new DOMParser();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const doc = sharedDOMParser.parseFromString(html, 'text/html');
|
|
285
|
+
|
|
286
|
+
const sanitize = (node) => {
|
|
287
|
+
const children = Array.from(node.childNodes);
|
|
288
|
+
children.forEach(child => {
|
|
289
|
+
if (child.nodeType === 1) {
|
|
290
|
+
if (DANGEROUS_TAGS.has(child.tagName.toLowerCase())) {
|
|
291
|
+
child.remove(); return;
|
|
292
|
+
}
|
|
293
|
+
const attrs = Array.from(child.attributes);
|
|
294
|
+
attrs.forEach(attr => {
|
|
295
|
+
const name = attr.name.toLowerCase();
|
|
296
|
+
const value = attr.value.toLowerCase().trim();
|
|
297
|
+
if (name.startsWith('on') || value.startsWith('javascript:') || value.startsWith('vbscript:') || value.startsWith('data:')) {
|
|
298
|
+
child.removeAttribute(attr.name);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
sanitize(child);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
sanitize(doc.body);
|
|
307
|
+
return doc.body.innerHTML;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const defaultIcons = {
|
|
311
|
+
error: '<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"></path></svg>',
|
|
312
|
+
warning: '<svg viewBox="0 0 24 24"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"></path></svg>',
|
|
313
|
+
success: '<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"></path></svg>',
|
|
314
|
+
info: '<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"></path></svg>',
|
|
315
|
+
processing: '<svg viewBox="0 0 24 24" class="kd-anm-rotate"><path d="M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8z"></path></svg>'
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Shows a notification and resolves a Promise when the notification is closed.
|
|
320
|
+
* Yields the clicked button's value, or true/null if dismissed without a button action.
|
|
321
|
+
*/
|
|
322
|
+
function show(options) {
|
|
323
|
+
injectCSS();
|
|
324
|
+
|
|
325
|
+
return new Promise(resolve => {
|
|
326
|
+
if (activeCloser) {
|
|
327
|
+
activeCloser(null);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (animationTimeout) {
|
|
331
|
+
clearTimeout(animationTimeout);
|
|
332
|
+
animationTimeout = null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Clean up any orphaned DOM elements from interrupted animations */
|
|
336
|
+
const existingOverlays = document.querySelectorAll('.kd-toast-overlay');
|
|
337
|
+
existingOverlays.forEach(el => {
|
|
338
|
+
if (document.body.contains(el)) {
|
|
339
|
+
document.body.removeChild(el);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const {
|
|
344
|
+
type: rawType = 'info',
|
|
345
|
+
title = '',
|
|
346
|
+
message = '',
|
|
347
|
+
duration = 3000,
|
|
348
|
+
buttons = null,
|
|
349
|
+
isModal = false,
|
|
350
|
+
icon = null,
|
|
351
|
+
style = {},
|
|
352
|
+
theme = 'auto',
|
|
353
|
+
position: rawPosition = 'center',
|
|
354
|
+
offsetX = '50px',
|
|
355
|
+
offsetY = '50px'
|
|
356
|
+
} = options;
|
|
357
|
+
|
|
358
|
+
const type = ALLOWED_TYPES.has(rawType) ? rawType : 'info';
|
|
359
|
+
const position = ALLOWED_POSITIONS.has(rawPosition) ? rawPosition : 'center';
|
|
360
|
+
|
|
361
|
+
const overlay = document.createElement('div');
|
|
362
|
+
overlay.className = `kd-toast-overlay kd-pos-${position}`;
|
|
363
|
+
|
|
364
|
+
if (position !== 'center') {
|
|
365
|
+
overlay.style.setProperty('--kd-toast-offset-x', typeof offsetX === 'number' ? `${offsetX}px` : offsetX);
|
|
366
|
+
overlay.style.setProperty('--kd-toast-offset-y', typeof offsetY === 'number' ? `${offsetY}px` : offsetY);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
let isLightMode = false;
|
|
370
|
+
if (theme === 'auto') {
|
|
371
|
+
const env = detectEnvironment();
|
|
372
|
+
isLightMode = env.isLight;
|
|
373
|
+
overlay.style.setProperty('--kd-font-family', env.font);
|
|
374
|
+
} else {
|
|
375
|
+
isLightMode = (theme === 'light');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
overlay.classList.add(isLightMode ? 'kd-theme-light' : 'kd-theme-dark');
|
|
379
|
+
|
|
380
|
+
const displayIcon = icon || defaultIcons[type] || defaultIcons.info;
|
|
381
|
+
let contentHTML = '';
|
|
382
|
+
|
|
383
|
+
if (title) {
|
|
384
|
+
contentHTML = `
|
|
385
|
+
<div class="kd-toast-rich-container">
|
|
386
|
+
<div class="kd-toast-rich-icon">${displayIcon}</div>
|
|
387
|
+
<div class="kd-toast-rich-content">
|
|
388
|
+
<span class="kd-toast-rich-title">${safeFormatMessage(title)}</span>
|
|
389
|
+
<span class="kd-toast-rich-text">${safeFormatMessage(message)}</span>
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
`;
|
|
393
|
+
} else {
|
|
394
|
+
contentHTML = `
|
|
395
|
+
<span class="kd-toast-custom-icon" style="display: ${displayIcon ? 'flex' : 'none'}">${displayIcon}</span>
|
|
396
|
+
<span class="kd-toast-text">${safeFormatMessage(message)}</span>
|
|
397
|
+
`;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
overlay.innerHTML = `
|
|
401
|
+
<div class="kd-toast kd-type-${type}">
|
|
402
|
+
<div class="kd-toast-content">
|
|
403
|
+
${contentHTML}
|
|
404
|
+
<div class="kd-toast-actions"></div>
|
|
405
|
+
</div>
|
|
406
|
+
</div>
|
|
407
|
+
`;
|
|
408
|
+
|
|
409
|
+
const toastEl = overlay.querySelector('.kd-toast');
|
|
410
|
+
|
|
411
|
+
if (style && typeof style === 'object') {
|
|
412
|
+
for (const key in style) {
|
|
413
|
+
if (Object.prototype.hasOwnProperty.call(style, key)) {
|
|
414
|
+
if (key.startsWith('--')) {
|
|
415
|
+
toastEl.style.setProperty(key, style[key]);
|
|
416
|
+
} else {
|
|
417
|
+
toastEl.style[key] = style[key];
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
document.body.appendChild(overlay);
|
|
424
|
+
|
|
425
|
+
const actionsEl = overlay.querySelector('.kd-toast-actions');
|
|
426
|
+
clearTimeout(notificationTimeout);
|
|
427
|
+
|
|
428
|
+
const dismiss = (value) => {
|
|
429
|
+
if (notificationTimeout) {
|
|
430
|
+
clearTimeout(notificationTimeout);
|
|
431
|
+
notificationTimeout = null;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
435
|
+
overlay.removeEventListener('click', overlayClickHandler);
|
|
436
|
+
|
|
437
|
+
const animDurationStr = window.getComputedStyle(overlay).getPropertyValue('--kd-toast-anim-duration');
|
|
438
|
+
const animDuration = parseInt(animDurationStr) || 250;
|
|
439
|
+
|
|
440
|
+
overlay.classList.remove('visible');
|
|
441
|
+
|
|
442
|
+
animationTimeout = setTimeout(() => {
|
|
443
|
+
if (document.body.contains(overlay)) {
|
|
444
|
+
document.body.removeChild(overlay);
|
|
445
|
+
}
|
|
446
|
+
animationTimeout = null;
|
|
447
|
+
}, animDuration);
|
|
448
|
+
|
|
449
|
+
activeCloser = null;
|
|
450
|
+
resolve(value);
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
activeCloser = dismiss;
|
|
454
|
+
|
|
455
|
+
const overlayClickHandler = (event) => {
|
|
456
|
+
if (event.target === overlay && !isModal) dismiss(null);
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
if (!isModal) {
|
|
460
|
+
overlay.addEventListener('click', overlayClickHandler);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const handleKeyDown = (e) => {
|
|
464
|
+
if (e.key === 'Escape' && !isModal) {
|
|
465
|
+
dismiss(null);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** Traps keyboard focus within the notification buttons to maintain accessibility standards */
|
|
470
|
+
if (e.key === 'Tab') {
|
|
471
|
+
const focusableElements = actionsEl.querySelectorAll('button');
|
|
472
|
+
if (focusableElements.length === 0) return;
|
|
473
|
+
|
|
474
|
+
const firstElement = focusableElements[0];
|
|
475
|
+
const lastElement = focusableElements[focusableElements.length - 1];
|
|
476
|
+
|
|
477
|
+
if (e.shiftKey) {
|
|
478
|
+
if (document.activeElement === firstElement) {
|
|
479
|
+
lastElement.focus();
|
|
480
|
+
e.preventDefault();
|
|
481
|
+
}
|
|
482
|
+
} else {
|
|
483
|
+
if (document.activeElement === lastElement) {
|
|
484
|
+
firstElement.focus();
|
|
485
|
+
e.preventDefault();
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
if (buttons && Array.isArray(buttons)) {
|
|
492
|
+
let elementToFocus = null;
|
|
493
|
+
|
|
494
|
+
buttons.forEach((buttonInfo, index) => {
|
|
495
|
+
const button = document.createElement('button');
|
|
496
|
+
button.textContent = buttonInfo.text;
|
|
497
|
+
|
|
498
|
+
if (buttonInfo.className) {
|
|
499
|
+
button.className = buttonInfo.className;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
button.onclick = () => {
|
|
503
|
+
if (typeof buttonInfo.onClick === 'function') buttonInfo.onClick();
|
|
504
|
+
setTimeout(() => dismiss(buttonInfo.value !== undefined ? buttonInfo.value : true), 0);
|
|
505
|
+
};
|
|
506
|
+
actionsEl.appendChild(button);
|
|
507
|
+
|
|
508
|
+
/** Determine which button gets priority focus (Primary wins over index 0) */
|
|
509
|
+
if (index === 0) {
|
|
510
|
+
elementToFocus = button;
|
|
511
|
+
} else if (buttonInfo.className && buttonInfo.className.includes('primary')) {
|
|
512
|
+
elementToFocus = button;
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
if (elementToFocus) {
|
|
517
|
+
setTimeout(() => elementToFocus.focus(), 50);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
521
|
+
} else if (!isModal && duration > 0) {
|
|
522
|
+
notificationTimeout = setTimeout(() => dismiss(true), duration);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/** Trigger Animation Reflow */
|
|
526
|
+
void overlay.offsetWidth;
|
|
527
|
+
overlay.classList.add('visible');
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** Programmatically dismisses the currently active notification, if any */
|
|
532
|
+
function close() {
|
|
533
|
+
if (activeCloser) {
|
|
534
|
+
activeCloser(null);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
show,
|
|
540
|
+
close
|
|
541
|
+
};
|
|
542
|
+
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(t){const n=function(){"use strict";let t=null,n=null,o=null;function e(){if(document.getElementById("kd-notification-styles"))return;const t="\n :root, .kd-theme-dark {\n --kd-accent-color: #4da6ff;\n --kd-accent-bg-transparent: rgba(77, 166, 255, 0.15);\n --kd-z-toast: 2147483647;\n --kd-bg-surface-elevated: #2c2c2c;\n --kd-bg-surface-toast: #2b2b2b;\n --kd-text-primary: #e3e3e3;\n --kd-text-secondary: #c4c7c5;\n --kd-border-subtle: rgba(255, 255, 255, 0.1);\n --kd-btn-bg: rgba(255, 255, 255, 0.08);\n --kd-color-success: #81c995;\n --kd-bg-success-transparent: rgba(46, 204, 113, 0.15);\n --kd-color-error: #f4877d;\n --kd-color-warning: #f1c40f;\n --kd-gradient-primary: linear-gradient(135deg, #a8c7fa, #8ab4f8);\n --kd-gradient-primary-hover: linear-gradient(135deg, #b8d4fc, #9bbdfc);\n --kd-gradient-danger: linear-gradient(135deg, #f4877d, #e74c3c);\n --kd-gradient-danger-hover: linear-gradient(135deg, #f6a199, #ec7063);\n --kd-toast-anim-duration: 250ms;\n --kd-font-family: system-ui, -apple-system, sans-serif;\n --kd-shadow-toast: 0 14px 40px rgba(0, 0, 0, 0.6);\n --kd-mobile-offset-x: 12px;\n --kd-mobile-offset-y: 16px;\n --kd-mobile-max-width: 420px;\n }\n\n .kd-theme-light {\n --kd-bg-surface-elevated: #f5f5f5;\n --kd-bg-surface-toast: #ffffff;\n --kd-text-primary: #1c1c1e;\n --kd-text-secondary: #5a5a5e;\n --kd-border-subtle: rgba(0, 0, 0, 0.1);\n --kd-btn-bg: rgba(0, 0, 0, 0.05);\n --kd-accent-color: #007bff;\n --kd-accent-bg-transparent: rgba(0, 123, 255, 0.15);\n --kd-shadow-toast: 0 14px 40px rgba(0, 0, 0, 0.15);\n }\n\n .kd-toast-overlay {\n position: fixed;\n top: 0; left: 0;\n width: 100%; height: 100%;\n z-index: var(--kd-z-toast);\n background-color: rgba(0, 0, 0, 0.5);\n display: flex;\n opacity: 0;\n will-change: opacity;\n transition: opacity var(--kd-toast-anim-duration) ease-in-out;\n pointer-events: auto;\n font-family: var(--kd-font-family);\n box-sizing: border-box;\n }\n\n .kd-toast-overlay.kd-pos-center { align-items: center; justify-content: center; padding: 0; }\n .kd-toast-overlay.kd-pos-top-left { align-items: flex-start; justify-content: flex-start; padding: var(--kd-toast-offset-y, 50px) var(--kd-toast-offset-x, 50px); }\n .kd-toast-overlay.kd-pos-top-right { align-items: flex-start; justify-content: flex-end; padding: var(--kd-toast-offset-y, 50px) var(--kd-toast-offset-x, 50px); }\n .kd-toast-overlay.kd-pos-bottom-left { align-items: flex-end; justify-content: flex-start; padding: var(--kd-toast-offset-y, 50px) var(--kd-toast-offset-x, 50px); }\n .kd-toast-overlay.kd-pos-bottom-right { align-items: flex-end; justify-content: flex-end; padding: var(--kd-toast-offset-y, 50px) var(--kd-toast-offset-x, 50px); }\n\n .kd-toast-overlay.visible { opacity: 1; }\n\n .kd-toast {\n pointer-events: auto;\n transform: scale(0.9);\n will-change: transform;\n background-color: var(--kd-bg-surface-toast);\n color: var(--kd-text-primary);\n padding: 28px;\n border-radius: 12px;\n border: 1px solid var(--kd-border-subtle);\n border-left: 3px solid transparent;\n box-shadow: var(--kd-shadow-toast);\n transition: transform var(--kd-toast-anim-duration) ease-in-out;\n min-width: 340px; max-width: 500px; min-height: 160px;\n display: flex; flex-direction: column; justify-content: center;\n box-sizing: border-box; overflow: hidden;\n }\n\n .kd-toast-overlay.visible .kd-toast { transform: scale(1); }\n\n .kd-toast-content {\n display: flex; flex-direction: column; align-items: center; gap: 12px;\n font-size: 15px; text-align: center;\n }\n\n .kd-toast-actions {\n display: flex; justify-content: center; gap: 16px; margin-top: 20px; width: 100%;\n }\n .kd-toast-actions:empty { display: none; }\n\n .kd-toast-actions button {\n font-size: 14px; padding: 10px 24px; border-radius: 24px; border: none;\n cursor: pointer; font-weight: 600; letter-spacing: 0.5px;\n font-family: inherit;\n box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);\n background-color: var(--kd-btn-bg); color: var(--kd-text-primary);\n border: 1px solid var(--kd-border-subtle);\n transition: transform 0.2s, box-shadow 0.2s, background-color 0.2s;\n }\n .kd-toast-actions button:hover {\n transform: translateY(-2px); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);\n background-color: var(--kd-bg-surface-elevated);\n }\n .kd-toast-actions button:active { transform: translateY(0); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); }\n \n .kd-toast-actions button:focus-visible {\n outline: 2px solid var(--kd-accent-color); outline-offset: 2px;\n }\n\n .kd-toast-actions .kd-btn-primary { background: var(--kd-gradient-primary); color: #000; }\n .kd-toast-actions .kd-btn-primary:hover { background: var(--kd-gradient-primary-hover); }\n \n .kd-toast-actions .kd-btn-danger { background: var(--kd-gradient-danger); color: #fff; }\n .kd-toast-actions .kd-btn-danger:hover { background: var(--kd-gradient-danger-hover); }\n\n .kd-toast-text { color: var(--kd-text-primary); line-height: 1.7em; letter-spacing: 0.5px; }\n .kd-toast:not(:has(.kd-toast-rich-container)) .kd-toast-text { text-align: center; }\n\n .kd-toast-rich-container { display: flex; align-items: flex-start; gap: 16px; width: 100%; text-align: left; }\n .kd-toast-rich-icon {\n flex-shrink: 0; width: 42px; height: 42px; display: flex; align-items: center; justify-content: center;\n border-radius: 50%; background-color: rgba(255, 255, 255, 0.1);\n }\n .kd-toast-rich-icon svg { width: 24px; height: 24px; fill: currentColor; }\n .kd-toast-rich-content { display: flex; flex-direction: column; justify-content: center; gap: 4px; flex-grow: 1; }\n .kd-toast-rich-title { font-weight: 700; font-size: 16px; color: var(--kd-text-primary); line-height: 1.2; }\n .kd-toast-rich-text { font-size: 14px; color: var(--kd-text-secondary); line-height: 1.4; }\n\n .kd-toast-custom-icon {\n display: none; width: 44px; height: 44px; border-radius: 50%; margin-bottom: 12px;\n align-self: center; margin-left: auto; margin-right: auto;\n justify-content: center; align-items: center;\n background: rgba(137, 180, 250, 0.1); color: var(--kd-accent-color); flex-shrink: 0;\n }\n .kd-toast-custom-icon svg { width: 24px; height: 24px; fill: currentColor; }\n\n .kd-toast.kd-type-error { border-left-color: var(--kd-color-error); box-shadow: var(--kd-shadow-toast), 0 0 20px rgba(244, 135, 125, 0.1); }\n .kd-toast.kd-type-error .kd-toast-rich-icon, .kd-toast.kd-type-error .kd-toast-custom-icon { color: var(--kd-color-error); background-color: rgba(244, 135, 125, 0.15); }\n \n .kd-toast.kd-type-warning { border-left-color: var(--kd-color-warning); box-shadow: var(--kd-shadow-toast), 0 0 20px rgba(241, 196, 15, 0.1); }\n .kd-toast.kd-type-warning .kd-toast-rich-icon, .kd-toast.kd-type-warning .kd-toast-custom-icon { color: var(--kd-color-warning); background-color: rgba(241, 196, 15, 0.15); }\n \n .kd-toast.kd-type-success { border-left-color: var(--kd-color-success); box-shadow: var(--kd-shadow-toast), 0 0 20px rgba(46, 204, 113, 0.1); }\n .kd-toast.kd-type-success .kd-toast-rich-icon, .kd-toast.kd-type-success .kd-toast-custom-icon { color: var(--kd-color-success); background-color: var(--kd-bg-success-transparent); }\n \n .kd-toast.kd-type-info, .kd-toast.kd-type-processing { border-left-color: var(--kd-accent-color); box-shadow: var(--kd-shadow-toast), 0 0 20px rgba(77, 166, 255, 0.1); }\n .kd-toast.kd-type-info .kd-toast-rich-icon, .kd-toast.kd-type-info .kd-toast-custom-icon, .kd-toast.kd-type-processing .kd-toast-custom-icon { color: var(--kd-accent-color); background-color: var(--kd-accent-bg-transparent); }\n\n .kd-toast.kd-type-info:has(.kd-toast-actions button) {\n background-color: var(--kd-bg-surface-elevated); padding: 28px 32px; border-radius: 24px; max-width: 500px; text-align: left;\n }\n .kd-toast.kd-type-info:has(.kd-toast-actions button) .kd-toast-actions { justify-content: flex-end; margin-top: 24px; }\n\n @keyframes kd-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }\n .kd-anm-rotate { animation: kd-spin 1s linear infinite; transform-origin: center; }\n\n @media (max-width: 600px) {\n .kd-toast-overlay {\n --kd-toast-offset-x: var(--kd-mobile-offset-x) !important;\n --kd-toast-offset-y: var(--kd-mobile-offset-y) !important;\n }\n .kd-toast-overlay.kd-pos-center {\n padding: var(--kd-toast-offset-y) var(--kd-toast-offset-x);\n }\n .kd-toast-overlay.kd-pos-top-left, \n .kd-toast-overlay.kd-pos-top-right {\n justify-content: center;\n }\n .kd-toast-overlay.kd-pos-bottom-left, \n .kd-toast-overlay.kd-pos-bottom-right {\n justify-content: center;\n }\n .kd-toast {\n min-width: 0;\n width: 100%;\n max-width: var(--kd-mobile-max-width);\n padding: 24px 20px;\n }\n .kd-toast.kd-type-info:has(.kd-toast-actions button) {\n padding: 24px 20px;\n max-width: var(--kd-mobile-max-width);\n }\n .kd-toast-actions {\n flex-direction: column;\n gap: 12px;\n }\n .kd-toast-actions button {\n width: 100%;\n padding: 14px 24px;\n }\n .kd-toast-rich-icon { width: 48px; height: 48px; }\n .kd-toast-rich-icon svg { width: 26px; height: 26px; }\n .kd-toast-custom-icon { width: 50px; height: 50px; margin-bottom: 16px; }\n .kd-toast-custom-icon svg { width: 28px; height: 28px; }\n .kd-toast-rich-title { font-size: 18px; }\n .kd-toast-rich-text { font-size: 15px; }\n .kd-toast-text { font-size: 16px; }\n }\n ",n=document.createElement("style");n.id="kd-notification-styles",n.textContent=t,document.head.appendChild(n)}function a(){const t=window.getComputedStyle(document.body),n=t.backgroundColor,o=t.fontFamily;let e=!1;const a=n.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);if(a){const t=parseInt(a[1],10),o=parseInt(a[2],10),s=parseInt(a[3],10),r=n.match(/rgba?\(\d+,\s*\d+,\s*\d+,\s*([\d.]+)/);if(0===(r?parseFloat(r[1]):1)||"transparent"===n)e=!window.matchMedia("(prefers-color-scheme: dark)").matches;else{e=(.299*t+.587*o+.114*s)/255>.5}}else e=!window.matchMedia("(prefers-color-scheme: dark)").matches;return{isLight:e,font:o}}const s=new Set(["script","object","embed","iframe","frameset","form","base","meta","link","style"]),r=new Set(["info","success","error","warning","processing"]),d=new Set(["center","top-left","top-right","bottom-left","bottom-right"]);let i=null;function c(t){if("string"!=typeof t)return"";i||(i=new DOMParser);const n=i.parseFromString(t,"text/html"),o=t=>{Array.from(t.childNodes).forEach(t=>{if(1===t.nodeType){if(s.has(t.tagName.toLowerCase()))return void t.remove();Array.from(t.attributes).forEach(n=>{const o=n.name.toLowerCase(),e=n.value.toLowerCase().trim();(o.startsWith("on")||e.startsWith("javascript:")||e.startsWith("vbscript:")||e.startsWith("data:"))&&t.removeAttribute(n.name)}),o(t)}})};return o(n.body),n.body.innerHTML}const l={error:'<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"></path></svg>',warning:'<svg viewBox="0 0 24 24"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"></path></svg>',success:'<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"></path></svg>',info:'<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"></path></svg>',processing:'<svg viewBox="0 0 24 24" class="kd-anm-rotate"><path d="M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8z"></path></svg>'};function k(s){return e(),new Promise(e=>{t&&t(null),o&&(clearTimeout(o),o=null);document.querySelectorAll(".kd-toast-overlay").forEach(t=>{document.body.contains(t)&&document.body.removeChild(t)});const{type:i="info",title:k="",message:p="",duration:f=3e3,buttons:g=null,isModal:m=!1,icon:u=null,style:h={},theme:x="auto",position:b="center",offsetX:v="50px",offsetY:y="50px"}=s,w=r.has(i)?i:"info",z=d.has(b)?b:"center",j=document.createElement("div");j.className=`kd-toast-overlay kd-pos-${z}`,"center"!==z&&(j.style.setProperty("--kd-toast-offset-x","number"==typeof v?`${v}px`:v),j.style.setProperty("--kd-toast-offset-y","number"==typeof y?`${y}px`:y));let C=!1;if("auto"===x){const t=a();C=t.isLight,j.style.setProperty("--kd-font-family",t.font)}else C="light"===x;j.classList.add(C?"kd-theme-light":"kd-theme-dark");const L=u||l[w]||l.info;let E="";E=k?`\n <div class="kd-toast-rich-container">\n <div class="kd-toast-rich-icon">${L}</div>\n <div class="kd-toast-rich-content">\n <span class="kd-toast-rich-title">${c(k)}</span>\n <span class="kd-toast-rich-text">${c(p)}</span>\n </div>\n </div>\n `:`\n <span class="kd-toast-custom-icon" style="display: ${L?"flex":"none"}">${L}</span>\n <span class="kd-toast-text">${c(p)}</span>\n `,j.innerHTML=`\n <div class="kd-toast kd-type-${w}">\n <div class="kd-toast-content">\n ${E}\n <div class="kd-toast-actions"></div>\n </div>\n </div>\n `;const T=j.querySelector(".kd-toast");if(h&&"object"==typeof h)for(const t in h)Object.prototype.hasOwnProperty.call(h,t)&&(t.startsWith("--")?T.style.setProperty(t,h[t]):T.style[t]=h[t]);document.body.appendChild(j);const S=j.querySelector(".kd-toast-actions");clearTimeout(n);const M=a=>{n&&(clearTimeout(n),n=null),document.removeEventListener("keydown",N),j.removeEventListener("click",$);const s=window.getComputedStyle(j).getPropertyValue("--kd-toast-anim-duration"),r=parseInt(s)||250;j.classList.remove("visible"),o=setTimeout(()=>{document.body.contains(j)&&document.body.removeChild(j),o=null},r),t=null,e(a)};t=M;const $=t=>{t.target!==j||m||M(null)};m||j.addEventListener("click",$);const N=t=>{if("Escape"!==t.key||m){if("Tab"===t.key){const n=S.querySelectorAll("button");if(0===n.length)return;const o=n[0],e=n[n.length-1];t.shiftKey?document.activeElement===o&&(e.focus(),t.preventDefault()):document.activeElement===e&&(o.focus(),t.preventDefault())}}else M(null)};if(g&&Array.isArray(g)){let t=null;g.forEach((n,o)=>{const e=document.createElement("button");e.textContent=n.text,n.className&&(e.className=n.className),e.onclick=()=>{"function"==typeof n.onClick&&n.onClick(),setTimeout(()=>M(void 0===n.value||n.value),0)},S.appendChild(e),(0===o||n.className&&n.className.includes("primary"))&&(t=e)}),t&&setTimeout(()=>t.focus(),50),document.addEventListener("keydown",N)}else!m&&f>0&&(n=setTimeout(()=>M(!0),f));j.offsetWidth,j.classList.add("visible")})}function p(){t&&t(null)}return{show:k,close:p}}();t.KDNotification=n,"object"==typeof module&&"object"==typeof module.exports?module.exports=n:"function"==typeof define&&define.amd&&define([],function(){return n})}("undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:this);
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "notifications-testi",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A standalone, ultra-lightweight, responsive & mobile-friendly toast notification system with a modern, elegant UI and zero dependencies.",
|
|
5
|
+
"main": "notifications-kd.js",
|
|
6
|
+
"module": "notifications-kd.js",
|
|
7
|
+
"unpkg": "notifications-kd.min.js",
|
|
8
|
+
"jsdelivr": "notifications-kd.min.js",
|
|
9
|
+
"types": "index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"notifications-kd.js",
|
|
12
|
+
"notifications-kd.min.js",
|
|
13
|
+
"index.d.ts"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "npx terser notifications-kd.js -o notifications-kd.min.js -c -m",
|
|
17
|
+
"prepublishOnly": "npm run build",
|
|
18
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/KhvichaDev/notifications-kd.git"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"toast",
|
|
26
|
+
"notification",
|
|
27
|
+
"alert",
|
|
28
|
+
"modal",
|
|
29
|
+
"popup",
|
|
30
|
+
"zero-dependency",
|
|
31
|
+
"vanilla-js"
|
|
32
|
+
],
|
|
33
|
+
"author": "KhvichaDev",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/KhvichaDev/notifications-kd/issues"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/KhvichaDev/notifications-kd#readme"
|
|
39
|
+
}
|