kimu-core 0.4.1 → 0.5.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/.editorconfig +116 -30
- package/.gitattributes +81 -11
- package/.github/FUNDING.yml +8 -8
- package/.github/kimu-copilot-instructions.md +3779 -3779
- package/.github/workflows/deploy-demo.yml +39 -39
- package/.nvmrc +1 -0
- package/.prettierignore +44 -0
- package/.prettierrc +16 -0
- package/FUNDING.md +31 -31
- package/icon.svg +10 -10
- package/kimu-core-0.5.0.tgz +0 -0
- package/package.json +10 -3
- package/scripts/minify-css-assets.js +82 -82
- package/src/core/index.ts +47 -47
- package/src/core/kimu-global-styles.ts +136 -136
- package/src/core/kimu-reactive.ts +196 -196
- package/src/extensions/{kimu-home → app-root}/component.ts +5 -5
- package/src/extensions/extensions-manifest.json +4 -4
- package/src/main.ts +3 -3
- package/src/modules-repository/api-axios/CHANGELOG.md +48 -48
- package/src/modules-repository/api-axios/QUICK-REFERENCE.md +178 -178
- package/src/modules-repository/api-axios/README.md +304 -304
- package/src/modules-repository/api-axios/api-axios-service.ts +355 -355
- package/src/modules-repository/api-axios/examples.ts +293 -293
- package/src/modules-repository/api-axios/index.ts +19 -19
- package/src/modules-repository/api-axios/interfaces.ts +71 -71
- package/src/modules-repository/api-axios/module.ts +41 -41
- package/src/modules-repository/api-core/CHANGELOG.md +42 -42
- package/src/modules-repository/api-core/QUICK-REFERENCE.md +192 -192
- package/src/modules-repository/api-core/README.md +435 -435
- package/src/modules-repository/api-core/api-core-service.ts +289 -289
- package/src/modules-repository/api-core/examples.ts +432 -432
- package/src/modules-repository/api-core/index.ts +8 -8
- package/src/modules-repository/api-core/interfaces.ts +83 -83
- package/src/modules-repository/api-core/module.ts +30 -30
- package/src/modules-repository/event-bus/README.md +273 -273
- package/src/modules-repository/event-bus/event-bus-service.ts +176 -176
- package/src/modules-repository/event-bus/module.ts +30 -30
- package/src/modules-repository/notification/README.md +423 -423
- package/src/modules-repository/notification/module.ts +30 -30
- package/src/modules-repository/notification/notification-service.ts +436 -436
- package/src/modules-repository/router/README.it.md +61 -10
- package/src/modules-repository/router/README.md +61 -10
- package/src/modules-repository/router/router-config.ts.example +61 -0
- package/src/modules-repository/router/router.ts +18 -0
- package/src/modules-repository/state/README.md +409 -409
- package/src/modules-repository/state/module.ts +30 -30
- package/src/modules-repository/state/state-service.ts +296 -296
- package/src/modules-repository/theme/README.md +311 -267
- package/src/modules-repository/theme/module.ts +30 -30
- package/src/modules-repository/theme/pre-build.js +40 -40
- package/src/modules-repository/theme/theme-service.ts +411 -389
- package/src/modules-repository/theme/themes/theme-cherry-blossom.css +78 -78
- package/src/modules-repository/theme/themes/theme-cozy.css +111 -111
- package/src/modules-repository/theme/themes/theme-cyberpunk.css +150 -150
- package/src/modules-repository/theme/themes/theme-dark.css +79 -79
- package/src/modules-repository/theme/themes/theme-forest.css +171 -171
- package/src/modules-repository/theme/themes/theme-gold.css +100 -100
- package/src/modules-repository/theme/themes/theme-high-contrast.css +126 -126
- package/src/modules-repository/theme/themes/theme-lava.css +101 -101
- package/src/modules-repository/theme/themes/theme-lavender.css +90 -90
- package/src/modules-repository/theme/themes/theme-light.css +79 -79
- package/src/modules-repository/theme/themes/theme-matrix.css +103 -103
- package/src/modules-repository/theme/themes/theme-midnight.css +81 -81
- package/src/modules-repository/theme/themes/theme-nord.css +94 -94
- package/src/modules-repository/theme/themes/theme-ocean.css +84 -84
- package/src/modules-repository/theme/themes/theme-retro80s.css +343 -343
- package/src/modules-repository/theme/themes/theme-sunset.css +62 -62
- package/src/modules-repository/theme/themes-config-default.json +19 -0
- package/src/modules-repository/theme/themes-config.d.ts +27 -27
- package/src/modules-repository/theme/{themes-config.json → themes-config.json.example} +223 -213
- /package/src/extensions/{kimu-home → app-root}/lang/en.json +0 -0
- /package/src/extensions/{kimu-home → app-root}/lang/it.json +0 -0
- /package/src/extensions/{kimu-home → app-root}/style.css +0 -0
- /package/src/extensions/{kimu-home → app-root}/view.html +0 -0
|
@@ -1,436 +1,436 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Notification Service for KIMU-Core
|
|
3
|
-
*
|
|
4
|
-
* Provides toast notifications, alerts, and user feedback system.
|
|
5
|
-
* Supports different notification types, durations, and positions.
|
|
6
|
-
*
|
|
7
|
-
* @module NotificationService
|
|
8
|
-
* @version 1.0.0
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export type NotificationType = 'success' | 'error' | 'warning' | 'info';
|
|
12
|
-
export type NotificationPosition = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center' | 'bottom-center';
|
|
13
|
-
|
|
14
|
-
export interface NotificationOptions {
|
|
15
|
-
type?: NotificationType;
|
|
16
|
-
duration?: number; // in milliseconds, 0 = no auto-dismiss
|
|
17
|
-
position?: NotificationPosition;
|
|
18
|
-
dismissible?: boolean;
|
|
19
|
-
icon?: string;
|
|
20
|
-
onClick?: () => void;
|
|
21
|
-
onClose?: () => void;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface Notification {
|
|
25
|
-
id: string;
|
|
26
|
-
message: string;
|
|
27
|
-
type: NotificationType;
|
|
28
|
-
duration: number;
|
|
29
|
-
position: NotificationPosition;
|
|
30
|
-
dismissible: boolean;
|
|
31
|
-
icon?: string;
|
|
32
|
-
timestamp: number;
|
|
33
|
-
element?: HTMLElement;
|
|
34
|
-
timeoutId?: number;
|
|
35
|
-
onClick?: () => void;
|
|
36
|
-
onClose?: () => void;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* NotificationService - Toast notifications and alerts
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* ```typescript
|
|
44
|
-
* import { notificationService } from './modules/notification/notification-service';
|
|
45
|
-
*
|
|
46
|
-
* // Show success notification
|
|
47
|
-
* notificationService.success('Operation completed!');
|
|
48
|
-
*
|
|
49
|
-
* // Show error with custom duration
|
|
50
|
-
* notificationService.error('Something went wrong!', { duration: 5000 });
|
|
51
|
-
*
|
|
52
|
-
* // Show info with custom position
|
|
53
|
-
* notificationService.info('New message received', { position: 'bottom-right' });
|
|
54
|
-
* ```
|
|
55
|
-
*/
|
|
56
|
-
export class NotificationService {
|
|
57
|
-
private notifications: Map<string, Notification> = new Map();
|
|
58
|
-
private container?: HTMLElement;
|
|
59
|
-
private defaultDuration: number = 3000;
|
|
60
|
-
private defaultPosition: NotificationPosition = 'top-right';
|
|
61
|
-
private maxNotifications: number = 5;
|
|
62
|
-
private debugMode: boolean = false;
|
|
63
|
-
|
|
64
|
-
constructor() {
|
|
65
|
-
this.initContainer();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Enable or disable debug mode
|
|
70
|
-
*/
|
|
71
|
-
setDebugMode(enabled: boolean): void {
|
|
72
|
-
this.debugMode = enabled;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Set default notification duration
|
|
77
|
-
*/
|
|
78
|
-
setDefaultDuration(duration: number): void {
|
|
79
|
-
this.defaultDuration = duration;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Set default notification position
|
|
84
|
-
*/
|
|
85
|
-
setDefaultPosition(position: NotificationPosition): void {
|
|
86
|
-
this.defaultPosition = position;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Set maximum number of visible notifications
|
|
91
|
-
*/
|
|
92
|
-
setMaxNotifications(max: number): void {
|
|
93
|
-
this.maxNotifications = max;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Show a success notification
|
|
98
|
-
*/
|
|
99
|
-
success(message: string, options?: NotificationOptions): string {
|
|
100
|
-
return this.show(message, { ...options, type: 'success' });
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Show an error notification
|
|
105
|
-
*/
|
|
106
|
-
error(message: string, options?: NotificationOptions): string {
|
|
107
|
-
return this.show(message, { ...options, type: 'error' });
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Show a warning notification
|
|
112
|
-
*/
|
|
113
|
-
warning(message: string, options?: NotificationOptions): string {
|
|
114
|
-
return this.show(message, { ...options, type: 'warning' });
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Show an info notification
|
|
119
|
-
*/
|
|
120
|
-
info(message: string, options?: NotificationOptions): string {
|
|
121
|
-
return this.show(message, { ...options, type: 'info' });
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Show a notification with custom options
|
|
126
|
-
*/
|
|
127
|
-
show(message: string, options: NotificationOptions = {}): string {
|
|
128
|
-
const id = this.generateId();
|
|
129
|
-
|
|
130
|
-
const notification: Notification = {
|
|
131
|
-
id,
|
|
132
|
-
message,
|
|
133
|
-
type: options.type || 'info',
|
|
134
|
-
duration: options.duration !== undefined ? options.duration : this.defaultDuration,
|
|
135
|
-
position: options.position || this.defaultPosition,
|
|
136
|
-
dismissible: options.dismissible !== undefined ? options.dismissible : true,
|
|
137
|
-
icon: options.icon || this.getDefaultIcon(options.type || 'info'),
|
|
138
|
-
timestamp: Date.now(),
|
|
139
|
-
onClick: options.onClick,
|
|
140
|
-
onClose: options.onClose
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
if (this.debugMode) {
|
|
144
|
-
console.log('[Notification] Show:', notification);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Remove oldest notification if max limit reached
|
|
148
|
-
if (this.notifications.size >= this.maxNotifications) {
|
|
149
|
-
const oldestId = Array.from(this.notifications.keys())[0];
|
|
150
|
-
this.dismiss(oldestId);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
this.notifications.set(id, notification);
|
|
154
|
-
this.renderNotification(notification);
|
|
155
|
-
|
|
156
|
-
// Auto-dismiss if duration > 0
|
|
157
|
-
if (notification.duration > 0) {
|
|
158
|
-
notification.timeoutId = window.setTimeout(() => {
|
|
159
|
-
this.dismiss(id);
|
|
160
|
-
}, notification.duration);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return id;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Dismiss a specific notification
|
|
168
|
-
*/
|
|
169
|
-
dismiss(id: string): void {
|
|
170
|
-
const notification = this.notifications.get(id);
|
|
171
|
-
if (!notification) return;
|
|
172
|
-
|
|
173
|
-
if (this.debugMode) {
|
|
174
|
-
console.log('[Notification] Dismiss:', id);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Clear timeout
|
|
178
|
-
if (notification.timeoutId) {
|
|
179
|
-
clearTimeout(notification.timeoutId);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Remove element with animation
|
|
183
|
-
if (notification.element) {
|
|
184
|
-
notification.element.classList.add('kimu-notification-exit');
|
|
185
|
-
setTimeout(() => {
|
|
186
|
-
notification.element?.remove();
|
|
187
|
-
}, 300);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Call onClose callback
|
|
191
|
-
if (notification.onClose) {
|
|
192
|
-
notification.onClose();
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
this.notifications.delete(id);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Dismiss all notifications
|
|
200
|
-
*/
|
|
201
|
-
dismissAll(): void {
|
|
202
|
-
const ids = Array.from(this.notifications.keys());
|
|
203
|
-
ids.forEach(id => this.dismiss(id));
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Get all active notifications
|
|
208
|
-
*/
|
|
209
|
-
getAll(): Notification[] {
|
|
210
|
-
return Array.from(this.notifications.values());
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Get notification count
|
|
215
|
-
*/
|
|
216
|
-
getCount(): number {
|
|
217
|
-
return this.notifications.size;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Private methods
|
|
221
|
-
|
|
222
|
-
private initContainer(): void {
|
|
223
|
-
if (typeof document === 'undefined') return;
|
|
224
|
-
|
|
225
|
-
this.container = document.createElement('div');
|
|
226
|
-
this.container.id = 'kimu-notification-container';
|
|
227
|
-
this.injectStyles();
|
|
228
|
-
document.body.appendChild(this.container);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
private renderNotification(notification: Notification): void {
|
|
232
|
-
if (!this.container) return;
|
|
233
|
-
|
|
234
|
-
const element = document.createElement('div');
|
|
235
|
-
element.className = `kimu-notification kimu-notification-${notification.type} kimu-notification-${notification.position}`;
|
|
236
|
-
element.setAttribute('data-id', notification.id);
|
|
237
|
-
|
|
238
|
-
const content = `
|
|
239
|
-
<div class="kimu-notification-content">
|
|
240
|
-
${notification.icon ? `<span class="kimu-notification-icon">${notification.icon}</span>` : ''}
|
|
241
|
-
<span class="kimu-notification-message">${this.escapeHtml(notification.message)}</span>
|
|
242
|
-
</div>
|
|
243
|
-
${notification.dismissible ? '<button class="kimu-notification-close" aria-label="Close">×</button>' : ''}
|
|
244
|
-
`;
|
|
245
|
-
|
|
246
|
-
element.innerHTML = content;
|
|
247
|
-
|
|
248
|
-
// Add click handlers
|
|
249
|
-
if (notification.onClick) {
|
|
250
|
-
element.addEventListener('click', (e) => {
|
|
251
|
-
if (!(e.target as HTMLElement).classList.contains('kimu-notification-close')) {
|
|
252
|
-
notification.onClick!();
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (notification.dismissible) {
|
|
258
|
-
const closeBtn = element.querySelector('.kimu-notification-close');
|
|
259
|
-
closeBtn?.addEventListener('click', (e) => {
|
|
260
|
-
e.stopPropagation();
|
|
261
|
-
this.dismiss(notification.id);
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
notification.element = element;
|
|
266
|
-
this.container.appendChild(element);
|
|
267
|
-
|
|
268
|
-
// Trigger animation
|
|
269
|
-
setTimeout(() => element.classList.add('kimu-notification-enter'), 10);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
private generateId(): string {
|
|
273
|
-
return `notification-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
private getDefaultIcon(type: NotificationType): string {
|
|
277
|
-
const icons = {
|
|
278
|
-
success: '✅',
|
|
279
|
-
error: '❌',
|
|
280
|
-
warning: '⚠️',
|
|
281
|
-
info: 'ℹ️'
|
|
282
|
-
};
|
|
283
|
-
return icons[type];
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
private escapeHtml(text: string): string {
|
|
287
|
-
const div = document.createElement('div');
|
|
288
|
-
div.textContent = text;
|
|
289
|
-
return div.innerHTML;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
private injectStyles(): void {
|
|
293
|
-
if (typeof document === 'undefined') return;
|
|
294
|
-
if (document.getElementById('kimu-notification-styles')) return;
|
|
295
|
-
|
|
296
|
-
const style = document.createElement('style');
|
|
297
|
-
style.id = 'kimu-notification-styles';
|
|
298
|
-
style.textContent = `
|
|
299
|
-
#kimu-notification-container {
|
|
300
|
-
position: fixed;
|
|
301
|
-
z-index: 9999;
|
|
302
|
-
pointer-events: none;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
.kimu-notification {
|
|
306
|
-
position: fixed;
|
|
307
|
-
min-width: 280px;
|
|
308
|
-
max-width: 400px;
|
|
309
|
-
padding: 16px 20px;
|
|
310
|
-
margin: 12px;
|
|
311
|
-
background: white;
|
|
312
|
-
border-radius: 8px;
|
|
313
|
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
314
|
-
display: flex;
|
|
315
|
-
align-items: center;
|
|
316
|
-
justify-content: space-between;
|
|
317
|
-
opacity: 0;
|
|
318
|
-
transform: translateY(-20px);
|
|
319
|
-
transition: all 0.3s ease;
|
|
320
|
-
pointer-events: auto;
|
|
321
|
-
cursor: default;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
.kimu-notification-enter {
|
|
325
|
-
opacity: 1;
|
|
326
|
-
transform: translateY(0);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
.kimu-notification-exit {
|
|
330
|
-
opacity: 0;
|
|
331
|
-
transform: translateY(-20px);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
.kimu-notification-content {
|
|
335
|
-
display: flex;
|
|
336
|
-
align-items: center;
|
|
337
|
-
gap: 12px;
|
|
338
|
-
flex: 1;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
.kimu-notification-icon {
|
|
342
|
-
font-size: 20px;
|
|
343
|
-
line-height: 1;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
.kimu-notification-message {
|
|
347
|
-
color: #333;
|
|
348
|
-
font-size: 14px;
|
|
349
|
-
line-height: 1.5;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
.kimu-notification-close {
|
|
353
|
-
background: none;
|
|
354
|
-
border: none;
|
|
355
|
-
font-size: 24px;
|
|
356
|
-
line-height: 1;
|
|
357
|
-
color: #999;
|
|
358
|
-
cursor: pointer;
|
|
359
|
-
padding: 0;
|
|
360
|
-
margin-left: 12px;
|
|
361
|
-
transition: color 0.2s;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
.kimu-notification-close:hover {
|
|
365
|
-
color: #333;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/* Types */
|
|
369
|
-
.kimu-notification-success {
|
|
370
|
-
background: #f0fdf4;
|
|
371
|
-
border-left: 4px solid #22c55e;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
.kimu-notification-error {
|
|
375
|
-
background: #fef2f2;
|
|
376
|
-
border-left: 4px solid #ef4444;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
.kimu-notification-warning {
|
|
380
|
-
background: #fffbeb;
|
|
381
|
-
border-left: 4px solid #f59e0b;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
.kimu-notification-info {
|
|
385
|
-
background: #eff6ff;
|
|
386
|
-
border-left: 4px solid #3b82f6;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/* Positions */
|
|
390
|
-
.kimu-notification-top-right {
|
|
391
|
-
top: 0;
|
|
392
|
-
right: 0;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
.kimu-notification-top-left {
|
|
396
|
-
top: 0;
|
|
397
|
-
left: 0;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
.kimu-notification-bottom-right {
|
|
401
|
-
bottom: 0;
|
|
402
|
-
right: 0;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
.kimu-notification-bottom-left {
|
|
406
|
-
bottom: 0;
|
|
407
|
-
left: 0;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
.kimu-notification-top-center {
|
|
411
|
-
top: 0;
|
|
412
|
-
left: 50%;
|
|
413
|
-
transform: translateX(-50%) translateY(-20px);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
.kimu-notification-top-center.kimu-notification-enter {
|
|
417
|
-
transform: translateX(-50%) translateY(0);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
.kimu-notification-bottom-center {
|
|
421
|
-
bottom: 0;
|
|
422
|
-
left: 50%;
|
|
423
|
-
transform: translateX(-50%) translateY(20px);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
.kimu-notification-bottom-center.kimu-notification-enter {
|
|
427
|
-
transform: translateX(-50%) translateY(0);
|
|
428
|
-
}
|
|
429
|
-
`;
|
|
430
|
-
|
|
431
|
-
document.head.appendChild(style);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// Export singleton instance
|
|
436
|
-
export const notificationService = new NotificationService();
|
|
1
|
+
/**
|
|
2
|
+
* Notification Service for KIMU-Core
|
|
3
|
+
*
|
|
4
|
+
* Provides toast notifications, alerts, and user feedback system.
|
|
5
|
+
* Supports different notification types, durations, and positions.
|
|
6
|
+
*
|
|
7
|
+
* @module NotificationService
|
|
8
|
+
* @version 1.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type NotificationType = 'success' | 'error' | 'warning' | 'info';
|
|
12
|
+
export type NotificationPosition = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center' | 'bottom-center';
|
|
13
|
+
|
|
14
|
+
export interface NotificationOptions {
|
|
15
|
+
type?: NotificationType;
|
|
16
|
+
duration?: number; // in milliseconds, 0 = no auto-dismiss
|
|
17
|
+
position?: NotificationPosition;
|
|
18
|
+
dismissible?: boolean;
|
|
19
|
+
icon?: string;
|
|
20
|
+
onClick?: () => void;
|
|
21
|
+
onClose?: () => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface Notification {
|
|
25
|
+
id: string;
|
|
26
|
+
message: string;
|
|
27
|
+
type: NotificationType;
|
|
28
|
+
duration: number;
|
|
29
|
+
position: NotificationPosition;
|
|
30
|
+
dismissible: boolean;
|
|
31
|
+
icon?: string;
|
|
32
|
+
timestamp: number;
|
|
33
|
+
element?: HTMLElement;
|
|
34
|
+
timeoutId?: number;
|
|
35
|
+
onClick?: () => void;
|
|
36
|
+
onClose?: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* NotificationService - Toast notifications and alerts
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* import { notificationService } from './modules/notification/notification-service';
|
|
45
|
+
*
|
|
46
|
+
* // Show success notification
|
|
47
|
+
* notificationService.success('Operation completed!');
|
|
48
|
+
*
|
|
49
|
+
* // Show error with custom duration
|
|
50
|
+
* notificationService.error('Something went wrong!', { duration: 5000 });
|
|
51
|
+
*
|
|
52
|
+
* // Show info with custom position
|
|
53
|
+
* notificationService.info('New message received', { position: 'bottom-right' });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export class NotificationService {
|
|
57
|
+
private notifications: Map<string, Notification> = new Map();
|
|
58
|
+
private container?: HTMLElement;
|
|
59
|
+
private defaultDuration: number = 3000;
|
|
60
|
+
private defaultPosition: NotificationPosition = 'top-right';
|
|
61
|
+
private maxNotifications: number = 5;
|
|
62
|
+
private debugMode: boolean = false;
|
|
63
|
+
|
|
64
|
+
constructor() {
|
|
65
|
+
this.initContainer();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Enable or disable debug mode
|
|
70
|
+
*/
|
|
71
|
+
setDebugMode(enabled: boolean): void {
|
|
72
|
+
this.debugMode = enabled;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Set default notification duration
|
|
77
|
+
*/
|
|
78
|
+
setDefaultDuration(duration: number): void {
|
|
79
|
+
this.defaultDuration = duration;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Set default notification position
|
|
84
|
+
*/
|
|
85
|
+
setDefaultPosition(position: NotificationPosition): void {
|
|
86
|
+
this.defaultPosition = position;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Set maximum number of visible notifications
|
|
91
|
+
*/
|
|
92
|
+
setMaxNotifications(max: number): void {
|
|
93
|
+
this.maxNotifications = max;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Show a success notification
|
|
98
|
+
*/
|
|
99
|
+
success(message: string, options?: NotificationOptions): string {
|
|
100
|
+
return this.show(message, { ...options, type: 'success' });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Show an error notification
|
|
105
|
+
*/
|
|
106
|
+
error(message: string, options?: NotificationOptions): string {
|
|
107
|
+
return this.show(message, { ...options, type: 'error' });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Show a warning notification
|
|
112
|
+
*/
|
|
113
|
+
warning(message: string, options?: NotificationOptions): string {
|
|
114
|
+
return this.show(message, { ...options, type: 'warning' });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Show an info notification
|
|
119
|
+
*/
|
|
120
|
+
info(message: string, options?: NotificationOptions): string {
|
|
121
|
+
return this.show(message, { ...options, type: 'info' });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Show a notification with custom options
|
|
126
|
+
*/
|
|
127
|
+
show(message: string, options: NotificationOptions = {}): string {
|
|
128
|
+
const id = this.generateId();
|
|
129
|
+
|
|
130
|
+
const notification: Notification = {
|
|
131
|
+
id,
|
|
132
|
+
message,
|
|
133
|
+
type: options.type || 'info',
|
|
134
|
+
duration: options.duration !== undefined ? options.duration : this.defaultDuration,
|
|
135
|
+
position: options.position || this.defaultPosition,
|
|
136
|
+
dismissible: options.dismissible !== undefined ? options.dismissible : true,
|
|
137
|
+
icon: options.icon || this.getDefaultIcon(options.type || 'info'),
|
|
138
|
+
timestamp: Date.now(),
|
|
139
|
+
onClick: options.onClick,
|
|
140
|
+
onClose: options.onClose
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
if (this.debugMode) {
|
|
144
|
+
console.log('[Notification] Show:', notification);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Remove oldest notification if max limit reached
|
|
148
|
+
if (this.notifications.size >= this.maxNotifications) {
|
|
149
|
+
const oldestId = Array.from(this.notifications.keys())[0];
|
|
150
|
+
this.dismiss(oldestId);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
this.notifications.set(id, notification);
|
|
154
|
+
this.renderNotification(notification);
|
|
155
|
+
|
|
156
|
+
// Auto-dismiss if duration > 0
|
|
157
|
+
if (notification.duration > 0) {
|
|
158
|
+
notification.timeoutId = window.setTimeout(() => {
|
|
159
|
+
this.dismiss(id);
|
|
160
|
+
}, notification.duration);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return id;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Dismiss a specific notification
|
|
168
|
+
*/
|
|
169
|
+
dismiss(id: string): void {
|
|
170
|
+
const notification = this.notifications.get(id);
|
|
171
|
+
if (!notification) return;
|
|
172
|
+
|
|
173
|
+
if (this.debugMode) {
|
|
174
|
+
console.log('[Notification] Dismiss:', id);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Clear timeout
|
|
178
|
+
if (notification.timeoutId) {
|
|
179
|
+
clearTimeout(notification.timeoutId);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Remove element with animation
|
|
183
|
+
if (notification.element) {
|
|
184
|
+
notification.element.classList.add('kimu-notification-exit');
|
|
185
|
+
setTimeout(() => {
|
|
186
|
+
notification.element?.remove();
|
|
187
|
+
}, 300);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Call onClose callback
|
|
191
|
+
if (notification.onClose) {
|
|
192
|
+
notification.onClose();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this.notifications.delete(id);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Dismiss all notifications
|
|
200
|
+
*/
|
|
201
|
+
dismissAll(): void {
|
|
202
|
+
const ids = Array.from(this.notifications.keys());
|
|
203
|
+
ids.forEach(id => this.dismiss(id));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Get all active notifications
|
|
208
|
+
*/
|
|
209
|
+
getAll(): Notification[] {
|
|
210
|
+
return Array.from(this.notifications.values());
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Get notification count
|
|
215
|
+
*/
|
|
216
|
+
getCount(): number {
|
|
217
|
+
return this.notifications.size;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Private methods
|
|
221
|
+
|
|
222
|
+
private initContainer(): void {
|
|
223
|
+
if (typeof document === 'undefined') return;
|
|
224
|
+
|
|
225
|
+
this.container = document.createElement('div');
|
|
226
|
+
this.container.id = 'kimu-notification-container';
|
|
227
|
+
this.injectStyles();
|
|
228
|
+
document.body.appendChild(this.container);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private renderNotification(notification: Notification): void {
|
|
232
|
+
if (!this.container) return;
|
|
233
|
+
|
|
234
|
+
const element = document.createElement('div');
|
|
235
|
+
element.className = `kimu-notification kimu-notification-${notification.type} kimu-notification-${notification.position}`;
|
|
236
|
+
element.setAttribute('data-id', notification.id);
|
|
237
|
+
|
|
238
|
+
const content = `
|
|
239
|
+
<div class="kimu-notification-content">
|
|
240
|
+
${notification.icon ? `<span class="kimu-notification-icon">${notification.icon}</span>` : ''}
|
|
241
|
+
<span class="kimu-notification-message">${this.escapeHtml(notification.message)}</span>
|
|
242
|
+
</div>
|
|
243
|
+
${notification.dismissible ? '<button class="kimu-notification-close" aria-label="Close">×</button>' : ''}
|
|
244
|
+
`;
|
|
245
|
+
|
|
246
|
+
element.innerHTML = content;
|
|
247
|
+
|
|
248
|
+
// Add click handlers
|
|
249
|
+
if (notification.onClick) {
|
|
250
|
+
element.addEventListener('click', (e) => {
|
|
251
|
+
if (!(e.target as HTMLElement).classList.contains('kimu-notification-close')) {
|
|
252
|
+
notification.onClick!();
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (notification.dismissible) {
|
|
258
|
+
const closeBtn = element.querySelector('.kimu-notification-close');
|
|
259
|
+
closeBtn?.addEventListener('click', (e) => {
|
|
260
|
+
e.stopPropagation();
|
|
261
|
+
this.dismiss(notification.id);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
notification.element = element;
|
|
266
|
+
this.container.appendChild(element);
|
|
267
|
+
|
|
268
|
+
// Trigger animation
|
|
269
|
+
setTimeout(() => element.classList.add('kimu-notification-enter'), 10);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private generateId(): string {
|
|
273
|
+
return `notification-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private getDefaultIcon(type: NotificationType): string {
|
|
277
|
+
const icons = {
|
|
278
|
+
success: '✅',
|
|
279
|
+
error: '❌',
|
|
280
|
+
warning: '⚠️',
|
|
281
|
+
info: 'ℹ️'
|
|
282
|
+
};
|
|
283
|
+
return icons[type];
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private escapeHtml(text: string): string {
|
|
287
|
+
const div = document.createElement('div');
|
|
288
|
+
div.textContent = text;
|
|
289
|
+
return div.innerHTML;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private injectStyles(): void {
|
|
293
|
+
if (typeof document === 'undefined') return;
|
|
294
|
+
if (document.getElementById('kimu-notification-styles')) return;
|
|
295
|
+
|
|
296
|
+
const style = document.createElement('style');
|
|
297
|
+
style.id = 'kimu-notification-styles';
|
|
298
|
+
style.textContent = `
|
|
299
|
+
#kimu-notification-container {
|
|
300
|
+
position: fixed;
|
|
301
|
+
z-index: 9999;
|
|
302
|
+
pointer-events: none;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.kimu-notification {
|
|
306
|
+
position: fixed;
|
|
307
|
+
min-width: 280px;
|
|
308
|
+
max-width: 400px;
|
|
309
|
+
padding: 16px 20px;
|
|
310
|
+
margin: 12px;
|
|
311
|
+
background: white;
|
|
312
|
+
border-radius: 8px;
|
|
313
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
314
|
+
display: flex;
|
|
315
|
+
align-items: center;
|
|
316
|
+
justify-content: space-between;
|
|
317
|
+
opacity: 0;
|
|
318
|
+
transform: translateY(-20px);
|
|
319
|
+
transition: all 0.3s ease;
|
|
320
|
+
pointer-events: auto;
|
|
321
|
+
cursor: default;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.kimu-notification-enter {
|
|
325
|
+
opacity: 1;
|
|
326
|
+
transform: translateY(0);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.kimu-notification-exit {
|
|
330
|
+
opacity: 0;
|
|
331
|
+
transform: translateY(-20px);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.kimu-notification-content {
|
|
335
|
+
display: flex;
|
|
336
|
+
align-items: center;
|
|
337
|
+
gap: 12px;
|
|
338
|
+
flex: 1;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.kimu-notification-icon {
|
|
342
|
+
font-size: 20px;
|
|
343
|
+
line-height: 1;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.kimu-notification-message {
|
|
347
|
+
color: #333;
|
|
348
|
+
font-size: 14px;
|
|
349
|
+
line-height: 1.5;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.kimu-notification-close {
|
|
353
|
+
background: none;
|
|
354
|
+
border: none;
|
|
355
|
+
font-size: 24px;
|
|
356
|
+
line-height: 1;
|
|
357
|
+
color: #999;
|
|
358
|
+
cursor: pointer;
|
|
359
|
+
padding: 0;
|
|
360
|
+
margin-left: 12px;
|
|
361
|
+
transition: color 0.2s;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.kimu-notification-close:hover {
|
|
365
|
+
color: #333;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/* Types */
|
|
369
|
+
.kimu-notification-success {
|
|
370
|
+
background: #f0fdf4;
|
|
371
|
+
border-left: 4px solid #22c55e;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.kimu-notification-error {
|
|
375
|
+
background: #fef2f2;
|
|
376
|
+
border-left: 4px solid #ef4444;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.kimu-notification-warning {
|
|
380
|
+
background: #fffbeb;
|
|
381
|
+
border-left: 4px solid #f59e0b;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.kimu-notification-info {
|
|
385
|
+
background: #eff6ff;
|
|
386
|
+
border-left: 4px solid #3b82f6;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/* Positions */
|
|
390
|
+
.kimu-notification-top-right {
|
|
391
|
+
top: 0;
|
|
392
|
+
right: 0;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.kimu-notification-top-left {
|
|
396
|
+
top: 0;
|
|
397
|
+
left: 0;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.kimu-notification-bottom-right {
|
|
401
|
+
bottom: 0;
|
|
402
|
+
right: 0;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.kimu-notification-bottom-left {
|
|
406
|
+
bottom: 0;
|
|
407
|
+
left: 0;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.kimu-notification-top-center {
|
|
411
|
+
top: 0;
|
|
412
|
+
left: 50%;
|
|
413
|
+
transform: translateX(-50%) translateY(-20px);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.kimu-notification-top-center.kimu-notification-enter {
|
|
417
|
+
transform: translateX(-50%) translateY(0);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.kimu-notification-bottom-center {
|
|
421
|
+
bottom: 0;
|
|
422
|
+
left: 50%;
|
|
423
|
+
transform: translateX(-50%) translateY(20px);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.kimu-notification-bottom-center.kimu-notification-enter {
|
|
427
|
+
transform: translateX(-50%) translateY(0);
|
|
428
|
+
}
|
|
429
|
+
`;
|
|
430
|
+
|
|
431
|
+
document.head.appendChild(style);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Export singleton instance
|
|
436
|
+
export const notificationService = new NotificationService();
|