@liwe3/webcomponents 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/AITextEditor.d.ts +173 -0
  2. package/dist/AITextEditor.d.ts.map +1 -0
  3. package/dist/ChunkUploader.d.ts +103 -0
  4. package/dist/ChunkUploader.d.ts.map +1 -0
  5. package/dist/ChunkUploader.js +614 -0
  6. package/dist/ChunkUploader.js.map +1 -0
  7. package/dist/ContainerBox.d.ts +112 -0
  8. package/dist/ContainerBox.d.ts.map +1 -0
  9. package/dist/ContainerBox.js +359 -0
  10. package/dist/ContainerBox.js.map +1 -0
  11. package/dist/DateSelector.d.ts +103 -0
  12. package/dist/DateSelector.d.ts.map +1 -0
  13. package/dist/DateSelector.js +372 -0
  14. package/dist/DateSelector.js.map +1 -0
  15. package/dist/Drawer.d.ts +63 -0
  16. package/dist/Drawer.d.ts.map +1 -0
  17. package/dist/Drawer.js +340 -0
  18. package/dist/Drawer.js.map +1 -0
  19. package/dist/ImageView.d.ts +42 -0
  20. package/dist/ImageView.d.ts.map +1 -0
  21. package/dist/ImageView.js +209 -0
  22. package/dist/ImageView.js.map +1 -0
  23. package/dist/PopoverMenu.d.ts +103 -0
  24. package/dist/PopoverMenu.d.ts.map +1 -0
  25. package/dist/PopoverMenu.js +312 -0
  26. package/dist/PopoverMenu.js.map +1 -0
  27. package/dist/SmartSelect.d.ts +99 -0
  28. package/dist/SmartSelect.d.ts.map +1 -0
  29. package/dist/SmartSelect.js.map +1 -1
  30. package/dist/Toast.d.ts +127 -0
  31. package/dist/Toast.d.ts.map +1 -0
  32. package/dist/Toast.js +507 -0
  33. package/dist/Toast.js.map +1 -0
  34. package/dist/TreeView.d.ts +84 -0
  35. package/dist/TreeView.d.ts.map +1 -0
  36. package/dist/TreeView.js +478 -0
  37. package/dist/TreeView.js.map +1 -0
  38. package/dist/index.d.ts +16 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +30 -6
  41. package/dist/index.js.map +1 -1
  42. package/package.json +43 -3
  43. package/src/ChunkUploader.ts +921 -0
  44. package/src/ContainerBox.ts +570 -0
  45. package/src/DateSelector.ts +550 -0
  46. package/src/Drawer.ts +435 -0
  47. package/src/ImageView.ts +265 -0
  48. package/src/PopoverMenu.ts +595 -0
  49. package/src/SmartSelect.ts +231 -231
  50. package/src/Toast.ts +834 -0
  51. package/src/TreeView.ts +673 -0
  52. package/src/index.ts +70 -3
package/src/Toast.ts ADDED
@@ -0,0 +1,834 @@
1
+ /**
2
+ * Toast Web Component
3
+ * A customizable toast notification system with multiple types, icons, buttons, and auto-dismiss
4
+ */
5
+
6
+ export type ToastType = 'info' | 'warning' | 'error' | 'success';
7
+
8
+ export type ToastPosition = 'TL' | 'T' | 'TR' | 'BL' | 'B' | 'BR';
9
+
10
+ export type ToastButton = {
11
+ label: string;
12
+ onClick: () => void;
13
+ };
14
+
15
+ export type ToastConfig = {
16
+ title: string;
17
+ text: string;
18
+ type?: ToastType;
19
+ icon?: string; // URL to icon/image
20
+ buttons?: ToastButton[];
21
+ closable?: boolean; // Show close X button
22
+ duration?: number; // Auto-dismiss after x milliseconds (0 = no auto-dismiss, default: 5000ms)
23
+ position?: ToastPosition; // Toast container position (default: 'TR')
24
+ onClose?: () => void;
25
+ };
26
+
27
+ export class ToastElement extends HTMLElement {
28
+ declare shadowRoot: ShadowRoot;
29
+ private config: ToastConfig = {
30
+ title: '',
31
+ text: '',
32
+ type: 'info',
33
+ closable: true,
34
+ duration: 5000
35
+ };
36
+ private autoCloseTimer?: number;
37
+ private remainingTime: number = 0;
38
+ private pauseTime: number = 0;
39
+ private progressBar?: HTMLElement;
40
+
41
+ constructor () {
42
+ super();
43
+ this.attachShadow( { mode: 'open' } );
44
+ }
45
+
46
+ static get observedAttributes (): string[] {
47
+ return [ 'title', 'text', 'type', 'icon', 'closable', 'duration', 'buttons' ];
48
+ }
49
+
50
+ attributeChangedCallback ( _name: string, oldValue: string | null, newValue: string | null ): void {
51
+ if ( oldValue !== newValue ) {
52
+ this.render();
53
+ }
54
+ }
55
+
56
+ connectedCallback (): void {
57
+ this.render();
58
+ this.startAutoCloseTimer();
59
+ }
60
+
61
+ disconnectedCallback (): void {
62
+ this.clearAutoCloseTimer();
63
+ }
64
+
65
+ get title (): string {
66
+ const attrTitle = this.getAttribute( 'title' );
67
+ const configTitle = this.config.title;
68
+
69
+ // If no title is provided or empty, use capitalized type
70
+ if ( ( !attrTitle || attrTitle.trim() === '' ) && ( !configTitle || configTitle.trim() === '' ) ) {
71
+ const type = this.type;
72
+ return type.charAt( 0 ).toUpperCase() + type.slice( 1 );
73
+ }
74
+
75
+ return attrTitle || configTitle;
76
+ }
77
+
78
+ set title ( value: string ) {
79
+ if ( value && value.trim() !== '' ) {
80
+ this.setAttribute( 'title', value );
81
+ this.config.title = value;
82
+ } else {
83
+ this.removeAttribute( 'title' );
84
+ this.config.title = '';
85
+ }
86
+ }
87
+
88
+ get text (): string {
89
+ return this.getAttribute( 'text' ) || this.config.text;
90
+ }
91
+
92
+ set text ( value: string ) {
93
+ this.setAttribute( 'text', value );
94
+ this.config.text = value;
95
+ }
96
+
97
+ get type (): ToastType {
98
+ const attr = this.getAttribute( 'type' );
99
+ return ( attr as ToastType ) || this.config.type || 'info';
100
+ }
101
+
102
+ set type ( value: ToastType ) {
103
+ this.setAttribute( 'type', value );
104
+ this.config.type = value;
105
+ }
106
+
107
+ get icon (): string | undefined {
108
+ return this.getAttribute( 'icon' ) || this.config.icon;
109
+ }
110
+
111
+ set icon ( value: string | undefined ) {
112
+ if ( value ) {
113
+ this.setAttribute( 'icon', value );
114
+ this.config.icon = value;
115
+ } else {
116
+ this.removeAttribute( 'icon' );
117
+ this.config.icon = undefined;
118
+ }
119
+ }
120
+
121
+ get closable (): boolean {
122
+ if ( this.hasAttribute( 'closable' ) ) {
123
+ return this.getAttribute( 'closable' ) !== 'false';
124
+ }
125
+ return this.config.closable !== false;
126
+ }
127
+
128
+ set closable ( value: boolean ) {
129
+ if ( value ) {
130
+ this.setAttribute( 'closable', 'true' );
131
+ } else {
132
+ this.setAttribute( 'closable', 'false' );
133
+ }
134
+ this.config.closable = value;
135
+ }
136
+
137
+ get duration (): number {
138
+ const attr = this.getAttribute( 'duration' );
139
+ if ( attr ) {
140
+ return parseInt( attr, 10 );
141
+ }
142
+ return this.config.duration ?? 5000;
143
+ }
144
+
145
+ set duration ( value: number ) {
146
+ this.setAttribute( 'duration', value.toString() );
147
+ this.config.duration = value;
148
+ }
149
+
150
+ get buttons (): ToastButton[] {
151
+ const attr = this.getAttribute( 'buttons' );
152
+ if ( attr ) {
153
+ try {
154
+ return JSON.parse( attr );
155
+ } catch ( e ) {
156
+ console.error( 'Invalid buttons format:', e );
157
+ return [];
158
+ }
159
+ }
160
+ return this.config.buttons || [];
161
+ }
162
+
163
+ set buttons ( value: ToastButton[] ) {
164
+ this.setAttribute( 'buttons', JSON.stringify( value ) );
165
+ this.config.buttons = value;
166
+ }
167
+
168
+ /**
169
+ * Shows the toast with the given configuration
170
+ */
171
+ show ( config: ToastConfig ): void {
172
+ this.config = { ...this.config, ...config };
173
+
174
+ // If buttons are present, force duration to 0 (user must interact to close)
175
+ if ( config.buttons && config.buttons.length > 0 ) {
176
+ this.config.duration = 0;
177
+ }
178
+
179
+ // Sync config to attributes
180
+ if ( config.title && config.title.trim() !== '' ) {
181
+ this.title = config.title;
182
+ } else {
183
+ // Clear title if not provided or empty
184
+ this.removeAttribute( 'title' );
185
+ this.config.title = '';
186
+ }
187
+ this.text = config.text;
188
+ if ( config.type ) this.type = config.type;
189
+ if ( config.icon !== undefined ) this.icon = config.icon;
190
+ if ( config.closable !== undefined ) this.closable = config.closable;
191
+ if ( config.buttons && config.buttons.length > 0 ) {
192
+ // Force duration to 0 when buttons are present
193
+ this.duration = 0;
194
+ } else if ( config.duration !== undefined ) {
195
+ this.duration = config.duration;
196
+ }
197
+ if ( config.buttons ) this.buttons = config.buttons;
198
+
199
+ this.render();
200
+ this.startAutoCloseTimer();
201
+ }
202
+
203
+ /**
204
+ * Closes the toast
205
+ */
206
+ close (): void {
207
+ this.clearAutoCloseTimer();
208
+
209
+ // Add closing animation
210
+ const container = this.shadowRoot.querySelector( '.toast-container' ) as HTMLElement;
211
+ if ( container ) {
212
+ // Use requestAnimationFrame to ensure smooth animation
213
+ requestAnimationFrame( () => {
214
+ container.classList.add( 'closing' );
215
+ } );
216
+
217
+ // Listen for animation end event for smoother transition
218
+ const handleAnimationEnd = () => {
219
+ container.removeEventListener( 'animationend', handleAnimationEnd );
220
+
221
+ // Animate the host element collapsing (height and margin to 0)
222
+ const hostElement = this as unknown as HTMLElement;
223
+ const currentHeight = hostElement.offsetHeight;
224
+
225
+ // Set explicit height for animation
226
+ hostElement.style.height = `${ currentHeight }px`;
227
+ hostElement.style.marginBottom = '12px';
228
+
229
+ // Force reflow
230
+ void hostElement.offsetHeight;
231
+
232
+ // Animate to 0
233
+ hostElement.style.height = '0px';
234
+ hostElement.style.marginBottom = '0px';
235
+ hostElement.style.opacity = '0';
236
+
237
+ // Wait for transition to complete, then remove
238
+ setTimeout( () => {
239
+ this.dispatchEvent( new CustomEvent( 'close' ) );
240
+ if ( this.config.onClose ) {
241
+ this.config.onClose();
242
+ }
243
+ this.remove();
244
+ }, 300 );
245
+ };
246
+
247
+ container.addEventListener( 'animationend', handleAnimationEnd );
248
+
249
+ // Fallback timeout in case animationend doesn't fire
250
+ setTimeout( () => {
251
+ if ( this.isConnected ) {
252
+ handleAnimationEnd();
253
+ }
254
+ }, 350 );
255
+ } else {
256
+ this.dispatchEvent( new CustomEvent( 'close' ) );
257
+ if ( this.config.onClose ) {
258
+ this.config.onClose();
259
+ }
260
+ this.remove();
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Starts the auto-close timer if duration is set
266
+ */
267
+ private startAutoCloseTimer (): void {
268
+ this.clearAutoCloseTimer();
269
+
270
+ if ( this.duration > 0 ) {
271
+ this.remainingTime = this.duration;
272
+ this.pauseTime = Date.now();
273
+ this.autoCloseTimer = window.setTimeout( () => {
274
+ this.close();
275
+ }, this.duration );
276
+
277
+ // Start progress bar animation
278
+ this.startProgressBarAnimation();
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Pauses the auto-close timer
284
+ */
285
+ private pauseAutoCloseTimer (): void {
286
+ if ( this.autoCloseTimer && this.duration > 0 ) {
287
+ clearTimeout( this.autoCloseTimer );
288
+ this.autoCloseTimer = undefined;
289
+ this.remainingTime -= Date.now() - this.pauseTime;
290
+
291
+ // Pause progress bar animation
292
+ this.pauseProgressBarAnimation();
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Resumes the auto-close timer
298
+ */
299
+ private resumeAutoCloseTimer (): void {
300
+ if ( !this.autoCloseTimer && this.remainingTime > 0 ) {
301
+ this.pauseTime = Date.now();
302
+ this.autoCloseTimer = window.setTimeout( () => {
303
+ this.close();
304
+ }, this.remainingTime );
305
+
306
+ // Resume progress bar animation
307
+ this.resumeProgressBarAnimation();
308
+ }
309
+ }
310
+
311
+ /**
312
+ * Clears the auto-close timer
313
+ */
314
+ private clearAutoCloseTimer (): void {
315
+ if ( this.autoCloseTimer ) {
316
+ clearTimeout( this.autoCloseTimer );
317
+ this.autoCloseTimer = undefined;
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Starts the progress bar animation
323
+ */
324
+ private startProgressBarAnimation (): void {
325
+ if ( !this.progressBar || this.duration <= 0 ) return;
326
+
327
+ // Reset and start the animation
328
+ this.progressBar.style.animation = 'none';
329
+ // Force a reflow to reset the animation
330
+ void this.progressBar.offsetWidth;
331
+ this.progressBar.style.animation = `shrinkProgress ${ this.duration }ms linear forwards`;
332
+ }
333
+
334
+ /**
335
+ * Pauses the progress bar animation
336
+ */
337
+ private pauseProgressBarAnimation (): void {
338
+ if ( !this.progressBar ) return;
339
+
340
+ // Get the current computed width as a percentage of the container
341
+ const computedStyle = window.getComputedStyle( this.progressBar );
342
+ const currentWidth = computedStyle.width;
343
+ const containerWidth = this.progressBar.parentElement?.offsetWidth || 1;
344
+ const widthPercent = ( parseFloat( currentWidth ) / containerWidth ) * 100;
345
+
346
+ // Stop the animation and set the width directly
347
+ this.progressBar.style.animation = 'none';
348
+ this.progressBar.style.width = `${ widthPercent }%`;
349
+ }
350
+
351
+ /**
352
+ * Resumes the progress bar animation
353
+ */
354
+ private resumeProgressBarAnimation (): void {
355
+ if ( !this.progressBar || this.remainingTime <= 0 ) return;
356
+
357
+ // Get current width as starting point
358
+ const computedStyle = window.getComputedStyle( this.progressBar );
359
+ const currentWidth = computedStyle.width;
360
+ const containerWidth = this.progressBar.parentElement?.offsetWidth || 1;
361
+ const currentPercent = ( parseFloat( currentWidth ) / containerWidth ) * 100;
362
+
363
+ // Calculate the duration based on the remaining percentage and remaining time
364
+ // The animation should take exactly remainingTime to go from currentPercent to 0
365
+ const adjustedDuration = this.remainingTime;
366
+
367
+ // Create a new keyframe animation from current position to 0
368
+ const animationName = `shrinkProgress-${ Date.now() }`;
369
+ const styleSheet = this.shadowRoot.styleSheets[ 0 ];
370
+ const keyframes = `
371
+ @keyframes ${ animationName } {
372
+ from {
373
+ width: ${ currentPercent }%;
374
+ }
375
+ to {
376
+ width: 0%;
377
+ }
378
+ }
379
+ `;
380
+
381
+ // Add the new keyframe rule
382
+ if ( styleSheet ) {
383
+ styleSheet.insertRule( keyframes, styleSheet.cssRules.length );
384
+ }
385
+
386
+ // Apply the animation
387
+ this.progressBar.style.animation = `${ animationName } ${ adjustedDuration }ms linear forwards`;
388
+ }
389
+
390
+ /**
391
+ * Gets the color scheme for the toast type
392
+ */
393
+ private getTypeColors (): { background: string; border: string; icon: string } {
394
+ const type = this.type;
395
+
396
+ switch ( type ) {
397
+ case 'success':
398
+ return {
399
+ background: 'var(--toast-success-background, #d4edda)',
400
+ border: 'var(--toast-success-border, #c3e6cb)',
401
+ icon: 'var(--toast-success-icon, #155724)'
402
+ };
403
+ case 'error':
404
+ return {
405
+ background: 'var(--toast-error-background, #f8d7da)',
406
+ border: 'var(--toast-error-border, #f5c6cb)',
407
+ icon: 'var(--toast-error-icon, #721c24)'
408
+ };
409
+ case 'warning':
410
+ return {
411
+ background: 'var(--toast-warning-background, #fff3cd)',
412
+ border: 'var(--toast-warning-border, #ffeaa7)',
413
+ icon: 'var(--toast-warning-icon, #856404)'
414
+ };
415
+ case 'info':
416
+ default:
417
+ return {
418
+ background: 'var(--toast-info-background, #d1ecf1)',
419
+ border: 'var(--toast-info-border, #bee5eb)',
420
+ icon: 'var(--toast-info-icon, #0c5460)'
421
+ };
422
+ }
423
+ }
424
+
425
+ /**
426
+ * Gets the default icon for the toast type
427
+ */
428
+ private getDefaultIcon (): string {
429
+ const type = this.type;
430
+
431
+ switch ( type ) {
432
+ case 'success':
433
+ return '✓';
434
+ case 'error':
435
+ return '✕';
436
+ case 'warning':
437
+ return '⚠';
438
+ case 'info':
439
+ default:
440
+ return 'ℹ';
441
+ }
442
+ }
443
+
444
+ /**
445
+ * Binds all event listeners
446
+ */
447
+ private bindEvents (): void {
448
+ // Handle close button click and button clicks
449
+ this.shadowRoot.addEventListener( 'click', ( e ) => {
450
+ const target = e.target as HTMLElement;
451
+
452
+ if ( target.closest( '.close-button' ) ) {
453
+ this.close();
454
+ } else if ( target.closest( '.toast-button' ) ) {
455
+ const buttonIndex = ( target.closest( '.toast-button' ) as HTMLElement ).dataset.index;
456
+ if ( buttonIndex !== undefined ) {
457
+ const button = this.buttons[ parseInt( buttonIndex, 10 ) ];
458
+ if ( button && button.onClick ) {
459
+ button.onClick();
460
+ }
461
+ }
462
+ }
463
+ } );
464
+
465
+ // Handle mouse enter/leave to pause/resume timer
466
+ const container = this.shadowRoot.querySelector( '.toast-container' );
467
+ if ( container ) {
468
+ container.addEventListener( 'mouseenter', () => {
469
+ this.pauseAutoCloseTimer();
470
+ } );
471
+
472
+ container.addEventListener( 'mouseleave', () => {
473
+ this.resumeAutoCloseTimer();
474
+ } );
475
+ }
476
+ }
477
+
478
+ /**
479
+ * Renders the component
480
+ */
481
+ private render (): void {
482
+ const colors = this.getTypeColors();
483
+ const iconContent = this.icon
484
+ ? `<img src="${ this.icon }" alt="Toast icon" class="toast-icon-img" />`
485
+ : `<span class="toast-icon-default">${ this.getDefaultIcon() }</span>`;
486
+
487
+ this.shadowRoot.innerHTML = `
488
+ <style>
489
+ :host {
490
+ display: block;
491
+ font-family: var(--font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
492
+ font-size: var(--font-size, 14px);
493
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
494
+ }
495
+
496
+ .toast-container {
497
+ display: flex;
498
+ flex-direction: column;
499
+ min-width: 300px;
500
+ max-width: 500px;
501
+ padding: 16px;
502
+ background: ${ colors.background };
503
+ border: 1px solid ${ colors.border };
504
+ border-radius: var(--toast-border-radius, 8px);
505
+ box-shadow: var(--toast-shadow, 0 4px 12px rgba(0, 0, 0, 0.15));
506
+ animation: slideIn 0.35s cubic-bezier(0.16, 1, 0.3, 1);
507
+ position: relative;
508
+ will-change: transform, opacity;
509
+ }
510
+
511
+ .toast-container.closing {
512
+ animation: slideOut 0.3s cubic-bezier(0.4, 0, 1, 1) forwards;
513
+ }
514
+
515
+ @keyframes slideIn {
516
+ from {
517
+ opacity: 0;
518
+ transform: translateY(-20px) scale(0.95);
519
+ }
520
+ to {
521
+ opacity: 1;
522
+ transform: translateY(0) scale(1);
523
+ }
524
+ }
525
+
526
+ @keyframes slideOut {
527
+ from {
528
+ opacity: 1;
529
+ transform: translateY(0) scale(1);
530
+ }
531
+ to {
532
+ opacity: 0;
533
+ transform: translateY(-20px) scale(0.95);
534
+ }
535
+ }
536
+
537
+ .toast-header {
538
+ display: flex;
539
+ align-items: flex-start;
540
+ gap: 12px;
541
+ margin-bottom: 8px;
542
+ }
543
+
544
+ .toast-icon {
545
+ flex-shrink: 0;
546
+ width: 24px;
547
+ height: 24px;
548
+ display: flex;
549
+ align-items: center;
550
+ justify-content: center;
551
+ color: ${ colors.icon };
552
+ }
553
+
554
+ .toast-icon-img {
555
+ width: 100%;
556
+ height: 100%;
557
+ object-fit: contain;
558
+ }
559
+
560
+ .toast-icon-default {
561
+ font-size: 20px;
562
+ font-weight: bold;
563
+ }
564
+
565
+ .toast-content {
566
+ flex: 1;
567
+ min-width: 0;
568
+ }
569
+
570
+ .toast-title {
571
+ font-weight: 600;
572
+ font-size: 16px;
573
+ margin: 0 0 4px 0;
574
+ color: var(--toast-title-color, #333);
575
+ }
576
+
577
+ .toast-text {
578
+ margin: 0;
579
+ color: var(--toast-text-color, #555);
580
+ line-height: 1.5;
581
+ word-wrap: break-word;
582
+ }
583
+
584
+ .close-button {
585
+ position: absolute;
586
+ top: 8px;
587
+ right: 8px;
588
+ width: 24px;
589
+ height: 24px;
590
+ display: flex;
591
+ align-items: center;
592
+ justify-content: center;
593
+ background: transparent;
594
+ border: none;
595
+ cursor: pointer;
596
+ font-size: 20px;
597
+ color: var(--toast-close-color, #666);
598
+ border-radius: 4px;
599
+ transition: background-color 0.2s, color 0.2s;
600
+ padding: 0;
601
+ }
602
+
603
+ .close-button:hover {
604
+ background-color: var(--toast-close-hover-background, rgba(0, 0, 0, 0.1));
605
+ color: var(--toast-close-hover-color, #333);
606
+ }
607
+
608
+ .toast-buttons {
609
+ display: flex;
610
+ gap: 8px;
611
+ justify-content: flex-end;
612
+ margin-top: 12px;
613
+ padding-top: 12px;
614
+ border-top: 1px solid var(--toast-button-border, rgba(0, 0, 0, 0.1));
615
+ }
616
+
617
+ .toast-button {
618
+ padding: 6px 16px;
619
+ border: 1px solid var(--toast-button-border-color, #ccc);
620
+ border-radius: var(--toast-button-border-radius, 4px);
621
+ background: var(--toast-button-background, white);
622
+ color: var(--toast-button-color, #333);
623
+ font-size: 14px;
624
+ cursor: pointer;
625
+ transition: background-color 0.2s, border-color 0.2s;
626
+ font-family: inherit;
627
+ }
628
+
629
+ .toast-button:hover {
630
+ background-color: var(--toast-button-hover-background, #f8f9fa);
631
+ border-color: var(--toast-button-hover-border-color, #999);
632
+ }
633
+
634
+ .toast-button:active {
635
+ background-color: var(--toast-button-active-background, #e9ecef);
636
+ }
637
+
638
+ .toast-progress-bar {
639
+ position: absolute;
640
+ bottom: 0;
641
+ left: 0;
642
+ height: 4px;
643
+ width: 100%;
644
+ background: var(--toast-progress-bar-color, rgba(0, 0, 0, 0.3));
645
+ border-bottom-left-radius: var(--toast-border-radius, 8px);
646
+ border-bottom-right-radius: var(--toast-border-radius, 8px);
647
+ transform-origin: left;
648
+ }
649
+
650
+ @keyframes shrinkProgress {
651
+ from {
652
+ width: 100%;
653
+ }
654
+ to {
655
+ width: 0%;
656
+ }
657
+ }
658
+ </style>
659
+
660
+ <div class="toast-container">
661
+ ${ this.closable ? '<button class="close-button" aria-label="Close">×</button>' : '' }
662
+
663
+ <div class="toast-header">
664
+ <div class="toast-icon">
665
+ ${ iconContent }
666
+ </div>
667
+ <div class="toast-content">
668
+ <h4 class="toast-title">${ this.title }</h4>
669
+ <p class="toast-text">${ this.text }</p>
670
+ </div>
671
+ </div>
672
+
673
+ ${ this.buttons.length > 0 ? `
674
+ <div class="toast-buttons">
675
+ ${ this.buttons.map( ( button, index ) => `
676
+ <button class="toast-button" data-index="${ index }">
677
+ ${ button.label }
678
+ </button>
679
+ `).join( '' ) }
680
+ </div>
681
+ ` : '' }
682
+
683
+ ${ this.duration > 0 ? '<div class="toast-progress-bar"></div>' : '' }
684
+ </div>
685
+ `;
686
+
687
+ // Store reference to progress bar
688
+ this.progressBar = this.shadowRoot.querySelector( '.toast-progress-bar' ) as HTMLElement;
689
+
690
+ this.bindEvents();
691
+ }
692
+ }
693
+
694
+ /**
695
+ * Conditionally defines the custom element if in a browser environment.
696
+ */
697
+ const defineToast = ( tagName: string = 'liwe3-toast' ): void => {
698
+ if ( typeof window !== 'undefined' && !window.customElements.get( tagName ) ) {
699
+ customElements.define( tagName, ToastElement );
700
+ }
701
+ };
702
+
703
+ // Auto-register with default tag name
704
+ defineToast();
705
+
706
+ /**
707
+ * Base container ID prefix for toast notifications
708
+ */
709
+ const CONTAINER_ID_PREFIX = 'liwe3-toast-container';
710
+
711
+ /**
712
+ * Gets the container positioning styles based on position
713
+ */
714
+ const getContainerStyles = ( position: ToastPosition ): { top?: string; bottom?: string; left?: string; right?: string; alignItems: string } => {
715
+ switch ( position ) {
716
+ case 'TL':
717
+ return { top: '20px', left: '20px', alignItems: 'flex-start' };
718
+ case 'T':
719
+ return { top: '20px', left: '50%', alignItems: 'center' };
720
+ case 'TR':
721
+ return { top: '20px', right: '20px', alignItems: 'flex-end' };
722
+ case 'BL':
723
+ return { bottom: '20px', left: '20px', alignItems: 'flex-start' };
724
+ case 'B':
725
+ return { bottom: '20px', left: '50%', alignItems: 'center' };
726
+ case 'BR':
727
+ return { bottom: '20px', right: '20px', alignItems: 'flex-end' };
728
+ default:
729
+ return { top: '20px', right: '20px', alignItems: 'flex-end' };
730
+ }
731
+ };
732
+
733
+ /**
734
+ * Creates or gets the toast container element for the specified position
735
+ */
736
+ const getToastContainer = ( position: ToastPosition = 'TR' ): HTMLElement => {
737
+ const containerId = `${ CONTAINER_ID_PREFIX }-${ position.toLowerCase() }`;
738
+ let container = document.getElementById( containerId );
739
+
740
+ if ( !container ) {
741
+ container = document.createElement( 'div' );
742
+ container.id = containerId;
743
+ container.style.position = 'fixed';
744
+ container.style.zIndex = '99999';
745
+ container.style.display = 'flex';
746
+ container.style.flexDirection = 'column';
747
+ container.style.maxWidth = '400px';
748
+ container.style.pointerEvents = 'none';
749
+
750
+ // Apply position-specific styles
751
+ const styles = getContainerStyles( position );
752
+ if ( styles.top ) container.style.top = styles.top;
753
+ if ( styles.bottom ) container.style.bottom = styles.bottom;
754
+ if ( styles.left ) container.style.left = styles.left;
755
+ if ( styles.right ) container.style.right = styles.right;
756
+ container.style.alignItems = styles.alignItems;
757
+
758
+ // For centered positions, apply transform to center horizontally
759
+ if ( position === 'T' || position === 'B' ) {
760
+ container.style.transform = 'translateX(-50%)';
761
+ }
762
+
763
+ // Add media query styles for mobile and smooth transitions
764
+ const styleId = `${ containerId }-styles`;
765
+ if ( !document.getElementById( styleId ) ) {
766
+ const style = document.createElement( 'style' );
767
+ style.id = styleId;
768
+ style.textContent = `
769
+ #${ containerId } > * {
770
+ margin-bottom: 12px;
771
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
772
+ overflow: hidden;
773
+ }
774
+
775
+ #${ containerId } > *:last-child {
776
+ margin-bottom: 0;
777
+ }
778
+
779
+ @media (max-width: 768px) {
780
+ #${ containerId } {
781
+ left: 20px !important;
782
+ right: 20px !important;
783
+ max-width: none !important;
784
+ transform: none !important;
785
+ }
786
+ }
787
+ `;
788
+ document.head.appendChild( style );
789
+ }
790
+
791
+ document.body.appendChild( container );
792
+ }
793
+
794
+ return container;
795
+ };
796
+
797
+ /**
798
+ * Shows a toast notification with the given configuration.
799
+ * This is the recommended way to display toasts.
800
+ *
801
+ * @param config - The toast configuration
802
+ * @returns The toast element instance
803
+ *
804
+ * @example
805
+ * ```typescript
806
+ * import { toastAdd } from '@liwe3/webcomponents';
807
+ *
808
+ * toastAdd({
809
+ * title: 'Success!',
810
+ * text: 'Your changes have been saved.',
811
+ * type: 'success',
812
+ * duration: 5000,
813
+ * position: 'TR' // Optional: top-right (default)
814
+ * });
815
+ * ```
816
+ */
817
+ const toastAdd = ( config: ToastConfig ): ToastElement => {
818
+ const position = config.position || 'TR';
819
+ const container = getToastContainer( position );
820
+ const toast = document.createElement( 'liwe3-toast' ) as ToastElement;
821
+
822
+ // Allow pointer events on individual toasts
823
+ toast.style.pointerEvents = 'auto';
824
+
825
+ // Show the toast with the provided config
826
+ toast.show( config );
827
+
828
+ // Add to container
829
+ container.appendChild( toast );
830
+
831
+ return toast;
832
+ };
833
+
834
+ export { defineToast, toastAdd };