@xosue-utils/services 0.1.4 → 0.2.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.
@@ -2,13 +2,14 @@ import * as i0 from '@angular/core';
2
2
  import { input, booleanAttribute, computed, Component, PLATFORM_ID, Inject, Injectable, InjectionToken, inject, ChangeDetectorRef, HostListener, ViewChild, Input, ChangeDetectionStrategy, SimpleChange, signal } from '@angular/core';
3
3
  import { LucideDynamicIcon } from '@lucide/angular';
4
4
  import * as i2 from '@angular/common';
5
- import { isPlatformBrowser, CommonModule } from '@angular/common';
6
- import { Subject, BehaviorSubject } from 'rxjs';
5
+ import { isPlatformBrowser, CommonModule, AsyncPipe } from '@angular/common';
6
+ import { Subject, BehaviorSubject, Subscription, merge, fromEvent } from 'rxjs';
7
7
  import { trigger, transition, style, animate } from '@angular/animations';
8
8
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
9
9
  import { DomSanitizer } from '@angular/platform-browser';
10
10
  import * as i3 from '@angular/router';
11
11
  import { RouterModule, Router, NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from '@angular/router';
12
+ import { map, startWith } from 'rxjs/operators';
12
13
 
13
14
  /**
14
15
  * Icono Lucide.
@@ -88,7 +89,7 @@ class AlertService {
88
89
  this.platformId = platformId;
89
90
  this.isBrowser = isPlatformBrowser(this.platformId);
90
91
  }
91
- showAlert(message) {
92
+ showAlert(message, presentation = 'card') {
92
93
  if (!this.isBrowser)
93
94
  return Promise.resolve(false);
94
95
  return new Promise((resolve) => {
@@ -96,6 +97,7 @@ class AlertService {
96
97
  id: ++this.idCounter,
97
98
  message,
98
99
  mode: 'normal',
100
+ presentation,
99
101
  resolve,
100
102
  };
101
103
  this.alertSubject.next(alert);
@@ -109,12 +111,17 @@ class AlertService {
109
111
  id: ++this.idCounter,
110
112
  message,
111
113
  mode: 'actions',
114
+ presentation: options?.presentation ?? 'card',
112
115
  resolve,
113
116
  onAcceptClick: options?.onAcceptClick,
114
117
  };
115
118
  this.alertSubject.next(alert);
116
119
  });
117
120
  }
121
+ /** Confirmación estilo Cupertino sheet (desde abajo). */
122
+ showSheetConfirm(message, options) {
123
+ return this.showConfirm(message, { ...options, presentation: 'sheet' });
124
+ }
118
125
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: AlertService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable });
119
126
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: AlertService, providedIn: 'root' });
120
127
  }
@@ -134,6 +141,9 @@ const XOSUE_UTILS_SERVICES_DEFAULTS = {
134
141
  sessionLoginPath: '/auth/login',
135
142
  sessionRegisterLabel: 'Registrarse',
136
143
  sessionLoginLabel: 'Iniciar Sesión',
144
+ copySuccessLabel: 'Copiado',
145
+ copyErrorLabel: 'No se pudo copiar',
146
+ networkOfflineLabel: 'Sin conexión a internet',
137
147
  toastSoundUrl: '/sounds/toast.mp3',
138
148
  toastErrorSoundUrl: '/sounds/error-toast.mp3',
139
149
  toastSuccessGifUrl: '/gifs/success-toast.gif',
@@ -215,8 +225,11 @@ class AlertServiceComponent {
215
225
  trackById(_index, item) {
216
226
  return item.id;
217
227
  }
228
+ isSheet(alert) {
229
+ return alert.presentation === 'sheet';
230
+ }
218
231
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: AlertServiceComponent, deps: [{ token: AlertService }], target: i0.ɵɵFactoryTarget.Component });
219
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.11", type: AlertServiceComponent, isStandalone: true, selector: "alert-service", ngImport: i0, template: "<div class=\"AlertOverlay\" *ngIf=\"alerts.length > 0\">\r\n <div\r\n class=\"Alert\"\r\n *ngFor=\"let alert of alerts; trackBy: trackById\"\r\n [@alertAnimation]\r\n >\r\n <div class=\"AlertContent\">\r\n <div class=\"AlertIcon\">\r\n <lucide-icon name=\"circle-alert\"></lucide-icon>\r\n </div>\r\n <div class=\"AlertMessage\">\r\n {{ alert.message }}\r\n </div>\r\n <div class=\"AlertActions\">\r\n <button\r\n *ngIf=\"alert.mode === 'normal'\"\r\n type=\"button\"\r\n class=\"AlertButton AcceptButton\"\r\n (click)=\"accept(alert)\"\r\n >\r\n {{ config.alertAcceptLabel }}\r\n </button>\r\n <ng-container *ngIf=\"alert.mode === 'actions'\">\r\n <button\r\n type=\"button\"\r\n class=\"AlertButton CancelButton\"\r\n (click)=\"cancel(alert)\"\r\n >\r\n {{ config.alertCancelLabel }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"AlertButton AcceptButton\"\r\n (click)=\"accept(alert)\"\r\n >\r\n {{ config.alertAcceptLabel }}\r\n </button>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".AlertOverlay{position:fixed;top:0;left:0;width:100%;height:100%;background-color:color-mix(in srgb,var(--text-base) 50%,transparent);display:flex;align-items:center;justify-content:center;z-index:10000;pointer-events:auto;padding:12px}.Alert{background-color:var(--surface);border-radius:var(--radius-lg);box-shadow:0 10px 25px color-mix(in srgb,var(--text-base) 20%,transparent);max-width:400px;width:100%;margin:12px;pointer-events:auto}.AlertContent{padding:12px;display:flex;flex-direction:column;gap:8px}.AlertIcon{display:flex;justify-content:center}.AlertIcon lucide-icon{--lucide-stroke: var(--icon-muted);font-size:2rem}.AlertIcon lucide-icon .lucide-svg{width:2rem;height:2rem}.AlertMessage{text-align:center;font-size:1rem;color:var(--text-1, var(--text-base));line-height:1.5}.AlertActions{display:flex;gap:4px;justify-content:flex-end;margin-top:4px}.AlertButton{padding:8px 16px;border:none;border-radius:var(--radius-md);font-size:.875rem;font-weight:500;cursor:pointer}.AcceptButton{background-color:var(--primary);color:var(--text-inverted)}.CancelButton{background-color:var(--bg, var(--surface-secondary, var(--surface-2)));color:var(--text-base)}\n"], dependencies: [{ kind: "component", type: LucideIconComponent, selector: "lucide-icon", inputs: ["name", "size", "spin", "filled"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], animations: [
232
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.11", type: AlertServiceComponent, isStandalone: true, selector: "alert-service", ngImport: i0, template: "<div class=\"AlertOverlay\" *ngIf=\"alerts.length > 0\" [class.AlertOverlay--sheet]=\"isSheet(alerts[0])\">\r\n <div\r\n class=\"Alert\"\r\n [class.Alert--sheet]=\"isSheet(alert)\"\r\n *ngFor=\"let alert of alerts; trackBy: trackById\"\r\n [@alertAnimation]\r\n >\r\n <div class=\"AlertContent\">\r\n <div class=\"AlertIcon\" *ngIf=\"!isSheet(alert)\">\r\n <lucide-icon name=\"circle-alert\"></lucide-icon>\r\n </div>\r\n <div class=\"AlertMessage\">\r\n {{ alert.message }}\r\n </div>\r\n <div class=\"AlertActions\" [class.AlertActions--sheet]=\"isSheet(alert)\">\r\n <button\r\n *ngIf=\"alert.mode === 'normal'\"\r\n type=\"button\"\r\n class=\"AlertButton AcceptButton\"\r\n (click)=\"accept(alert)\"\r\n >\r\n {{ config.alertAcceptLabel }}\r\n </button>\r\n <ng-container *ngIf=\"alert.mode === 'actions'\">\r\n <button\r\n type=\"button\"\r\n class=\"AlertButton CancelButton\"\r\n (click)=\"cancel(alert)\"\r\n >\r\n {{ config.alertCancelLabel }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"AlertButton AcceptButton\"\r\n (click)=\"accept(alert)\"\r\n >\r\n {{ config.alertAcceptLabel }}\r\n </button>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".AlertOverlay{position:fixed;inset:0;background-color:var(--overlay-scrim, color-mix(in srgb, var(--text-base) 40%, transparent));backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;z-index:10000;pointer-events:auto;padding:16px}.AlertOverlay--sheet{align-items:flex-end;padding:8px;padding-bottom:max(8px,env(safe-area-inset-bottom,8px))}.Alert{background-color:var(--surface);border-radius:var(--radius-xl, 16px);border:1px solid var(--border);box-shadow:0 12px 40px color-mix(in srgb,var(--text-base) 18%,transparent);max-width:360px;width:100%;pointer-events:auto;overflow:hidden}.Alert--sheet{max-width:480px;border-radius:var(--radius-xl, 20px);margin-bottom:0}.AlertContent{padding:16px;display:flex;flex-direction:column;gap:8px}.AlertIcon{display:flex;justify-content:center}.AlertIcon lucide-icon{--lucide-stroke: var(--icon-muted, var(--text-muted));font-size:2rem}.AlertIcon lucide-icon .lucide-svg{width:2rem;height:2rem}.AlertMessage{text-align:center;font-size:.9375rem;color:var(--text-1, var(--text-base));line-height:1.45}.AlertActions{display:flex;gap:4px;justify-content:flex-end;margin-top:4px}.AlertActions--sheet{flex-direction:column-reverse;gap:4px}.AlertActions--sheet .AlertButton{width:100%;justify-content:center}.AlertButton{display:inline-flex;align-items:center;height:32px;padding:4px 16px;border:none;border-radius:9999px;font-size:.875rem;font-weight:600;cursor:pointer;transition:transform .1s ease,opacity .15s ease}.AlertButton:active{transform:scale(.98)}.AcceptButton{background-color:var(--primary);color:var(--text-inverted)}.CancelButton{background-color:var(--surface-2, var(--surface-secondary, var(--bg)));color:var(--text-base)}\n"], dependencies: [{ kind: "component", type: LucideIconComponent, selector: "lucide-icon", inputs: ["name", "size", "spin", "filled"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], animations: [
220
233
  trigger('alertAnimation', [
221
234
  transition(':enter', [
222
235
  style({ opacity: 0, transform: 'scale(0.9)' }),
@@ -240,7 +253,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImpo
240
253
  animate('200ms ease-in', style({ opacity: 0, transform: 'scale(0.9)' })),
241
254
  ]),
242
255
  ]),
243
- ], template: "<div class=\"AlertOverlay\" *ngIf=\"alerts.length > 0\">\r\n <div\r\n class=\"Alert\"\r\n *ngFor=\"let alert of alerts; trackBy: trackById\"\r\n [@alertAnimation]\r\n >\r\n <div class=\"AlertContent\">\r\n <div class=\"AlertIcon\">\r\n <lucide-icon name=\"circle-alert\"></lucide-icon>\r\n </div>\r\n <div class=\"AlertMessage\">\r\n {{ alert.message }}\r\n </div>\r\n <div class=\"AlertActions\">\r\n <button\r\n *ngIf=\"alert.mode === 'normal'\"\r\n type=\"button\"\r\n class=\"AlertButton AcceptButton\"\r\n (click)=\"accept(alert)\"\r\n >\r\n {{ config.alertAcceptLabel }}\r\n </button>\r\n <ng-container *ngIf=\"alert.mode === 'actions'\">\r\n <button\r\n type=\"button\"\r\n class=\"AlertButton CancelButton\"\r\n (click)=\"cancel(alert)\"\r\n >\r\n {{ config.alertCancelLabel }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"AlertButton AcceptButton\"\r\n (click)=\"accept(alert)\"\r\n >\r\n {{ config.alertAcceptLabel }}\r\n </button>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".AlertOverlay{position:fixed;top:0;left:0;width:100%;height:100%;background-color:color-mix(in srgb,var(--text-base) 50%,transparent);display:flex;align-items:center;justify-content:center;z-index:10000;pointer-events:auto;padding:12px}.Alert{background-color:var(--surface);border-radius:var(--radius-lg);box-shadow:0 10px 25px color-mix(in srgb,var(--text-base) 20%,transparent);max-width:400px;width:100%;margin:12px;pointer-events:auto}.AlertContent{padding:12px;display:flex;flex-direction:column;gap:8px}.AlertIcon{display:flex;justify-content:center}.AlertIcon lucide-icon{--lucide-stroke: var(--icon-muted);font-size:2rem}.AlertIcon lucide-icon .lucide-svg{width:2rem;height:2rem}.AlertMessage{text-align:center;font-size:1rem;color:var(--text-1, var(--text-base));line-height:1.5}.AlertActions{display:flex;gap:4px;justify-content:flex-end;margin-top:4px}.AlertButton{padding:8px 16px;border:none;border-radius:var(--radius-md);font-size:.875rem;font-weight:500;cursor:pointer}.AcceptButton{background-color:var(--primary);color:var(--text-inverted)}.CancelButton{background-color:var(--bg, var(--surface-secondary, var(--surface-2)));color:var(--text-base)}\n"] }]
256
+ ], template: "<div class=\"AlertOverlay\" *ngIf=\"alerts.length > 0\" [class.AlertOverlay--sheet]=\"isSheet(alerts[0])\">\r\n <div\r\n class=\"Alert\"\r\n [class.Alert--sheet]=\"isSheet(alert)\"\r\n *ngFor=\"let alert of alerts; trackBy: trackById\"\r\n [@alertAnimation]\r\n >\r\n <div class=\"AlertContent\">\r\n <div class=\"AlertIcon\" *ngIf=\"!isSheet(alert)\">\r\n <lucide-icon name=\"circle-alert\"></lucide-icon>\r\n </div>\r\n <div class=\"AlertMessage\">\r\n {{ alert.message }}\r\n </div>\r\n <div class=\"AlertActions\" [class.AlertActions--sheet]=\"isSheet(alert)\">\r\n <button\r\n *ngIf=\"alert.mode === 'normal'\"\r\n type=\"button\"\r\n class=\"AlertButton AcceptButton\"\r\n (click)=\"accept(alert)\"\r\n >\r\n {{ config.alertAcceptLabel }}\r\n </button>\r\n <ng-container *ngIf=\"alert.mode === 'actions'\">\r\n <button\r\n type=\"button\"\r\n class=\"AlertButton CancelButton\"\r\n (click)=\"cancel(alert)\"\r\n >\r\n {{ config.alertCancelLabel }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"AlertButton AcceptButton\"\r\n (click)=\"accept(alert)\"\r\n >\r\n {{ config.alertAcceptLabel }}\r\n </button>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".AlertOverlay{position:fixed;inset:0;background-color:var(--overlay-scrim, color-mix(in srgb, var(--text-base) 40%, transparent));backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;z-index:10000;pointer-events:auto;padding:16px}.AlertOverlay--sheet{align-items:flex-end;padding:8px;padding-bottom:max(8px,env(safe-area-inset-bottom,8px))}.Alert{background-color:var(--surface);border-radius:var(--radius-xl, 16px);border:1px solid var(--border);box-shadow:0 12px 40px color-mix(in srgb,var(--text-base) 18%,transparent);max-width:360px;width:100%;pointer-events:auto;overflow:hidden}.Alert--sheet{max-width:480px;border-radius:var(--radius-xl, 20px);margin-bottom:0}.AlertContent{padding:16px;display:flex;flex-direction:column;gap:8px}.AlertIcon{display:flex;justify-content:center}.AlertIcon lucide-icon{--lucide-stroke: var(--icon-muted, var(--text-muted));font-size:2rem}.AlertIcon lucide-icon .lucide-svg{width:2rem;height:2rem}.AlertMessage{text-align:center;font-size:.9375rem;color:var(--text-1, var(--text-base));line-height:1.45}.AlertActions{display:flex;gap:4px;justify-content:flex-end;margin-top:4px}.AlertActions--sheet{flex-direction:column-reverse;gap:4px}.AlertActions--sheet .AlertButton{width:100%;justify-content:center}.AlertButton{display:inline-flex;align-items:center;height:32px;padding:4px 16px;border:none;border-radius:9999px;font-size:.875rem;font-weight:600;cursor:pointer;transition:transform .1s ease,opacity .15s ease}.AlertButton:active{transform:scale(.98)}.AcceptButton{background-color:var(--primary);color:var(--text-inverted)}.CancelButton{background-color:var(--surface-2, var(--surface-secondary, var(--bg)));color:var(--text-base)}\n"] }]
244
257
  }], ctorParameters: () => [{ type: AlertService }] });
245
258
 
246
259
  class ToastService {
@@ -312,7 +325,7 @@ class ToastServiceComponent {
312
325
  return item.id;
313
326
  }
314
327
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: ToastServiceComponent, deps: [{ token: ToastService }], target: i0.ɵɵFactoryTarget.Component });
315
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.11", type: ToastServiceComponent, isStandalone: true, selector: "toast-service", ngImport: i0, template: "<div class=\"ToastOverlay\">\r\n <div class=\"ToastContainer\">\r\n <div\r\n class=\"Toast\"\r\n *ngFor=\"let toast of toasts; trackBy: trackById\"\r\n [@toastAnimation]\r\n [ngClass]=\"toast.type\"\r\n (click)=\"removeToast(toast.id)\"\r\n >\r\n <div class=\"Icon\" [ngSwitch]=\"toast.type\">\r\n <lucide-icon name=\"info\" *ngSwitchCase=\"'info'\"></lucide-icon>\r\n <lucide-icon name=\"circle-check\" *ngSwitchCase=\"'success'\"></lucide-icon>\r\n <lucide-icon name=\"circle-alert\" *ngSwitchCase=\"'error'\"></lucide-icon>\r\n <lucide-icon name=\"triangle-alert\" *ngSwitchCase=\"'warning'\"></lucide-icon>\r\n <lucide-icon name=\"bell\" *ngSwitchDefault></lucide-icon>\r\n </div>\r\n\r\n <div class=\"Text\">\r\n {{ toast.message }}\r\n </div>\r\n\r\n <img\r\n *ngIf=\"toast.type === 'success' && config.toastSuccessGifUrl\"\r\n class=\"SuccessToastGif\"\r\n [attr.src]=\"successGifSrc(toast)\"\r\n alt=\"\"\r\n aria-hidden=\"true\"\r\n />\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".ToastOverlay{position:fixed;top:0;left:0;width:100%;pointer-events:none;z-index:9999}.ToastContainer{display:flex;width:calc(100% - 20px);max-width:500px;flex-direction:column;gap:8px;margin:0 auto;padding:16px;pointer-events:none;align-items:center}.Toast{padding:12px;border:1px solid;border-radius:var(--radius-lg);cursor:pointer;font-weight:400;display:flex;gap:8px;pointer-events:auto;position:relative;overflow:visible}.SuccessToastGif{position:absolute;right:-5px;bottom:0;transform:translate(40%,10px);width:72px;height:72px;object-fit:contain;pointer-events:none}.Toast.success{background:var(--success-bg);color:var(--success-text);border-color:var(--success-border)}.Toast.warning{background:var(--warning-bg);color:var(--warning-text);border-color:var(--warning-border)}.Toast.error{background:var(--error-bg);color:var(--error-text);border-color:var(--error-border)}.Toast.info{background:var(--info-bg);color:var(--info-text);border-color:var(--info-border)}\n"], dependencies: [{ kind: "component", type: LucideIconComponent, selector: "lucide-icon", inputs: ["name", "size", "spin", "filled"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i2.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i2.NgSwitchDefault, selector: "[ngSwitchDefault]" }], animations: [
328
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.11", type: ToastServiceComponent, isStandalone: true, selector: "toast-service", ngImport: i0, template: "<div class=\"ToastOverlay\">\r\n <div class=\"ToastContainer\">\r\n <div\r\n class=\"Toast\"\r\n *ngFor=\"let toast of toasts; trackBy: trackById\"\r\n [@toastAnimation]\r\n [ngClass]=\"toast.type\"\r\n (click)=\"removeToast(toast.id)\"\r\n >\r\n <div class=\"Icon\" [ngSwitch]=\"toast.type\">\r\n <lucide-icon name=\"info\" *ngSwitchCase=\"'info'\"></lucide-icon>\r\n <lucide-icon name=\"circle-check\" *ngSwitchCase=\"'success'\"></lucide-icon>\r\n <lucide-icon name=\"circle-alert\" *ngSwitchCase=\"'error'\"></lucide-icon>\r\n <lucide-icon name=\"triangle-alert\" *ngSwitchCase=\"'warning'\"></lucide-icon>\r\n <lucide-icon name=\"bell\" *ngSwitchDefault></lucide-icon>\r\n </div>\r\n\r\n <div class=\"Text\">\r\n {{ toast.message }}\r\n </div>\r\n\r\n <img\r\n *ngIf=\"toast.type === 'success' && config.toastSuccessGifUrl\"\r\n class=\"SuccessToastGif\"\r\n [attr.src]=\"successGifSrc(toast)\"\r\n alt=\"\"\r\n aria-hidden=\"true\"\r\n />\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".ToastOverlay{position:fixed;top:0;left:0;width:100%;pointer-events:none;z-index:9999}.ToastContainer{display:flex;width:calc(100% - 20px);max-width:420px;flex-direction:column;gap:8px;margin:0 auto;padding:12px;pointer-events:none;align-items:center}.Toast{padding:8px 12px;border:1px solid;border-radius:var(--radius-xl, 16px);cursor:pointer;font-weight:500;font-size:.875rem;display:flex;gap:8px;align-items:center;pointer-events:auto;position:relative;overflow:visible;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);box-shadow:0 8px 24px color-mix(in srgb,var(--text-base) 12%,transparent);transition:transform .1s ease}.Toast:active{transform:scale(.98)}.SuccessToastGif{position:absolute;right:-5px;bottom:0;transform:translate(40%,10px);width:72px;height:72px;object-fit:contain;pointer-events:none}.Toast.success{background:color-mix(in srgb,var(--success-bg) 88%,var(--surface));color:var(--success-text);border-color:var(--success-border)}.Toast.warning{background:color-mix(in srgb,var(--warning-bg) 88%,var(--surface));color:var(--warning-text);border-color:var(--warning-border)}.Toast.error{background:color-mix(in srgb,var(--error-bg) 88%,var(--surface));color:var(--error-text);border-color:var(--error-border)}.Toast.info{background:color-mix(in srgb,var(--info-bg) 88%,var(--surface));color:var(--info-text);border-color:var(--info-border)}\n"], dependencies: [{ kind: "component", type: LucideIconComponent, selector: "lucide-icon", inputs: ["name", "size", "spin", "filled"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i2.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i2.NgSwitchDefault, selector: "[ngSwitchDefault]" }], animations: [
316
329
  trigger('toastAnimation', [
317
330
  transition(':enter', [
318
331
  style({ opacity: 0, transform: 'translateY(-20px)' }),
@@ -336,7 +349,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImpo
336
349
  animate('300ms ease-in', style({ opacity: 0, transform: 'translateY(-20px)' })),
337
350
  ]),
338
351
  ]),
339
- ], template: "<div class=\"ToastOverlay\">\r\n <div class=\"ToastContainer\">\r\n <div\r\n class=\"Toast\"\r\n *ngFor=\"let toast of toasts; trackBy: trackById\"\r\n [@toastAnimation]\r\n [ngClass]=\"toast.type\"\r\n (click)=\"removeToast(toast.id)\"\r\n >\r\n <div class=\"Icon\" [ngSwitch]=\"toast.type\">\r\n <lucide-icon name=\"info\" *ngSwitchCase=\"'info'\"></lucide-icon>\r\n <lucide-icon name=\"circle-check\" *ngSwitchCase=\"'success'\"></lucide-icon>\r\n <lucide-icon name=\"circle-alert\" *ngSwitchCase=\"'error'\"></lucide-icon>\r\n <lucide-icon name=\"triangle-alert\" *ngSwitchCase=\"'warning'\"></lucide-icon>\r\n <lucide-icon name=\"bell\" *ngSwitchDefault></lucide-icon>\r\n </div>\r\n\r\n <div class=\"Text\">\r\n {{ toast.message }}\r\n </div>\r\n\r\n <img\r\n *ngIf=\"toast.type === 'success' && config.toastSuccessGifUrl\"\r\n class=\"SuccessToastGif\"\r\n [attr.src]=\"successGifSrc(toast)\"\r\n alt=\"\"\r\n aria-hidden=\"true\"\r\n />\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".ToastOverlay{position:fixed;top:0;left:0;width:100%;pointer-events:none;z-index:9999}.ToastContainer{display:flex;width:calc(100% - 20px);max-width:500px;flex-direction:column;gap:8px;margin:0 auto;padding:16px;pointer-events:none;align-items:center}.Toast{padding:12px;border:1px solid;border-radius:var(--radius-lg);cursor:pointer;font-weight:400;display:flex;gap:8px;pointer-events:auto;position:relative;overflow:visible}.SuccessToastGif{position:absolute;right:-5px;bottom:0;transform:translate(40%,10px);width:72px;height:72px;object-fit:contain;pointer-events:none}.Toast.success{background:var(--success-bg);color:var(--success-text);border-color:var(--success-border)}.Toast.warning{background:var(--warning-bg);color:var(--warning-text);border-color:var(--warning-border)}.Toast.error{background:var(--error-bg);color:var(--error-text);border-color:var(--error-border)}.Toast.info{background:var(--info-bg);color:var(--info-text);border-color:var(--info-border)}\n"] }]
352
+ ], template: "<div class=\"ToastOverlay\">\r\n <div class=\"ToastContainer\">\r\n <div\r\n class=\"Toast\"\r\n *ngFor=\"let toast of toasts; trackBy: trackById\"\r\n [@toastAnimation]\r\n [ngClass]=\"toast.type\"\r\n (click)=\"removeToast(toast.id)\"\r\n >\r\n <div class=\"Icon\" [ngSwitch]=\"toast.type\">\r\n <lucide-icon name=\"info\" *ngSwitchCase=\"'info'\"></lucide-icon>\r\n <lucide-icon name=\"circle-check\" *ngSwitchCase=\"'success'\"></lucide-icon>\r\n <lucide-icon name=\"circle-alert\" *ngSwitchCase=\"'error'\"></lucide-icon>\r\n <lucide-icon name=\"triangle-alert\" *ngSwitchCase=\"'warning'\"></lucide-icon>\r\n <lucide-icon name=\"bell\" *ngSwitchDefault></lucide-icon>\r\n </div>\r\n\r\n <div class=\"Text\">\r\n {{ toast.message }}\r\n </div>\r\n\r\n <img\r\n *ngIf=\"toast.type === 'success' && config.toastSuccessGifUrl\"\r\n class=\"SuccessToastGif\"\r\n [attr.src]=\"successGifSrc(toast)\"\r\n alt=\"\"\r\n aria-hidden=\"true\"\r\n />\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".ToastOverlay{position:fixed;top:0;left:0;width:100%;pointer-events:none;z-index:9999}.ToastContainer{display:flex;width:calc(100% - 20px);max-width:420px;flex-direction:column;gap:8px;margin:0 auto;padding:12px;pointer-events:none;align-items:center}.Toast{padding:8px 12px;border:1px solid;border-radius:var(--radius-xl, 16px);cursor:pointer;font-weight:500;font-size:.875rem;display:flex;gap:8px;align-items:center;pointer-events:auto;position:relative;overflow:visible;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);box-shadow:0 8px 24px color-mix(in srgb,var(--text-base) 12%,transparent);transition:transform .1s ease}.Toast:active{transform:scale(.98)}.SuccessToastGif{position:absolute;right:-5px;bottom:0;transform:translate(40%,10px);width:72px;height:72px;object-fit:contain;pointer-events:none}.Toast.success{background:color-mix(in srgb,var(--success-bg) 88%,var(--surface));color:var(--success-text);border-color:var(--success-border)}.Toast.warning{background:color-mix(in srgb,var(--warning-bg) 88%,var(--surface));color:var(--warning-text);border-color:var(--warning-border)}.Toast.error{background:color-mix(in srgb,var(--error-bg) 88%,var(--surface));color:var(--error-text);border-color:var(--error-border)}.Toast.info{background:color-mix(in srgb,var(--info-bg) 88%,var(--surface));color:var(--info-text);border-color:var(--info-border)}\n"] }]
340
353
  }], ctorParameters: () => [{ type: ToastService }] });
341
354
 
342
355
  class ContextMenuService {
@@ -554,11 +567,11 @@ class ContextMenuServiceComponent {
554
567
  this.cdr.detectChanges();
555
568
  }
556
569
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: ContextMenuServiceComponent, deps: [{ token: ContextMenuService }, { token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Component });
557
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.11", type: ContextMenuServiceComponent, isStandalone: true, selector: "context-menu-service", host: { listeners: { "window:resize": "onWindowResize()", "document:keydown": "onEscape($event)" } }, viewQueries: [{ propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true }], ngImport: i0, template: "@if (active) {\r\n <div class=\"ContextMenuOverlay\" aria-hidden=\"true\">\r\n <div\r\n #panel\r\n class=\"ContextMenuPanel\"\r\n [class.ContextMenuPanel--visible]=\"panelVisible\"\r\n [class.ContextMenuPanel--menu]=\"active.kind === 'menu'\"\r\n [class.ContextMenuPanel--info]=\"active.kind === 'info'\"\r\n [style.left.px]=\"position.x\"\r\n [style.top.px]=\"position.y\"\r\n role=\"menu\"\r\n [attr.aria-label]=\"active.kind === 'info' ? active.info.title : (active.title || 'Opciones')\"\r\n [attr.aria-hidden]=\"!panelVisible\"\r\n (contextmenu)=\"$event.preventDefault()\"\r\n >\r\n @if (active.kind === 'menu') {\r\n @if (active.title) {\r\n <p class=\"ContextMenuMenuTitle\">{{ active.title }}</p>\r\n }\r\n @for (option of active.options; track $index) {\r\n @if (option.separatorBefore) {\r\n <div class=\"ContextMenuMenuSep\" aria-hidden=\"true\"></div>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"ContextMenuMenuItem\"\r\n [class.ContextMenuMenuItem--danger]=\"option.danger\"\r\n [class.ContextMenuMenuItem--disabled]=\"option.disabled\"\r\n [disabled]=\"option.disabled\"\r\n role=\"menuitem\"\r\n (click)=\"selectOption(option)\"\r\n >\r\n <lucide-icon [name]=\"option.icon\" [size]=\"14\"></lucide-icon>\r\n <span>{{ option.name }}</span>\r\n </button>\r\n }\r\n } @else {\r\n <div class=\"ContextMenuInfo\">\r\n <div class=\"ContextMenuInfoHeader\">\r\n <lucide-icon [name]=\"active.info.icon\" [size]=\"18\"></lucide-icon>\r\n <strong>{{ active.info.title }}</strong>\r\n </div>\r\n <p class=\"ContextMenuInfoBody\">{{ active.info.info }}</p>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n}\r\n", styles: [".ContextMenuOverlay{position:fixed;inset:5px;pointer-events:none;z-index:10001}.ContextMenuPanel{position:fixed;pointer-events:none;visibility:hidden;opacity:0;min-width:10rem;max-width:min(18rem,100vw - 10px);max-height:calc(100vh - 10px);overflow-x:hidden;overflow-y:auto;background-color:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);box-shadow:0 8px 24px var(--shadow-color);transition:opacity .1s ease-out}.ContextMenuPanel--visible{visibility:visible;opacity:1;pointer-events:auto}.ContextMenuPanel--menu{min-width:220px;max-width:min(280px,100vw - 16px);padding:4px;border-radius:12px;border:1px solid var(--border);background:var(--surface);box-shadow:0 12px 40px var(--shadow-color),0 0 0 .5px var(--border);display:flex;flex-direction:column;gap:2px;animation:cupertino-menu-in .1s ease-out}.ContextMenuMenuTitle{margin:0;padding:4px 8px 2px;font-size:.7rem;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ContextMenuMenuSep{height:1px;margin:2px 4px;background:var(--surface-2, var(--border))}.ContextMenuMenuItem{display:flex;align-items:center;gap:8px;width:100%;padding:4px 8px;border:none;border-radius:8px;background:transparent;color:var(--text-base);font:inherit;font-size:.875rem;text-align:left;cursor:pointer}.ContextMenuMenuItem lucide-icon{flex-shrink:0;--lucide-stroke: var(--icon-base)}.ContextMenuMenuItem:hover,.ContextMenuMenuItem:focus-visible{outline:none;background:var(--info-bg, var(--primary-soft, var(--surface-2)))}.ContextMenuMenuItem--danger{color:var(--error-text)}.ContextMenuMenuItem--danger lucide-icon{--lucide-stroke: var(--error-text)}.ContextMenuMenuItem--danger:hover,.ContextMenuMenuItem--danger:focus-visible{background:var(--error-bg)}.ContextMenuMenuItem--disabled,.ContextMenuMenuItem:disabled{opacity:.45;cursor:not-allowed;pointer-events:none}.ContextMenuPanel--info{padding:0}.ContextMenuInfo{padding:12px;display:flex;flex-direction:column;gap:8px}.ContextMenuInfoHeader{display:flex;align-items:center;gap:8px;color:var(--text-base)}.ContextMenuInfoHeader strong{font-size:.9rem;font-weight:600}.ContextMenuInfoHeader lucide-icon{flex-shrink:0;--lucide-stroke: var(--primary)}.ContextMenuInfoBody{margin:0;font-size:.82rem;line-height:1.45;color:var(--text-label)}@keyframes cupertino-menu-in{0%{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: LucideIconComponent, selector: "lucide-icon", inputs: ["name", "size", "spin", "filled"] }] });
570
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.11", type: ContextMenuServiceComponent, isStandalone: true, selector: "context-menu-service", host: { listeners: { "window:resize": "onWindowResize()", "document:keydown": "onEscape($event)" } }, viewQueries: [{ propertyName: "panelRef", first: true, predicate: ["panel"], descendants: true }], ngImport: i0, template: "@if (active) {\r\n <div class=\"ContextMenuOverlay\" aria-hidden=\"true\">\r\n <div\r\n #panel\r\n class=\"ContextMenuPanel\"\r\n [class.ContextMenuPanel--visible]=\"panelVisible\"\r\n [class.ContextMenuPanel--menu]=\"active.kind === 'menu'\"\r\n [class.ContextMenuPanel--info]=\"active.kind === 'info'\"\r\n [style.left.px]=\"position.x\"\r\n [style.top.px]=\"position.y\"\r\n role=\"menu\"\r\n [attr.aria-label]=\"active.kind === 'info' ? active.info.title : (active.title || 'Opciones')\"\r\n [attr.aria-hidden]=\"!panelVisible\"\r\n (contextmenu)=\"$event.preventDefault()\"\r\n >\r\n @if (active.kind === 'menu') {\r\n @if (active.title) {\r\n <p class=\"ContextMenuMenuTitle\">{{ active.title }}</p>\r\n }\r\n @for (option of active.options; track $index) {\r\n @if (option.separatorBefore) {\r\n <div class=\"ContextMenuMenuSep\" aria-hidden=\"true\"></div>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"ContextMenuMenuItem\"\r\n [class.ContextMenuMenuItem--danger]=\"option.danger\"\r\n [class.ContextMenuMenuItem--disabled]=\"option.disabled\"\r\n [disabled]=\"option.disabled\"\r\n role=\"menuitem\"\r\n (click)=\"selectOption(option)\"\r\n >\r\n <lucide-icon [name]=\"option.icon\" [size]=\"14\"></lucide-icon>\r\n <span>{{ option.name }}</span>\r\n </button>\r\n }\r\n } @else {\r\n <div class=\"ContextMenuInfo\">\r\n <div class=\"ContextMenuInfoHeader\">\r\n <lucide-icon [name]=\"active.info.icon\" [size]=\"18\"></lucide-icon>\r\n <strong>{{ active.info.title }}</strong>\r\n </div>\r\n <p class=\"ContextMenuInfoBody\">{{ active.info.info }}</p>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n}\r\n", styles: [".ContextMenuOverlay{position:fixed;inset:5px;pointer-events:none;z-index:10001}.ContextMenuPanel{position:fixed;pointer-events:none;visibility:hidden;opacity:0;min-width:10rem;max-width:min(18rem,100vw - 10px);max-height:calc(100vh - 10px);overflow-x:hidden;overflow-y:auto;background-color:color-mix(in srgb,var(--surface) 88%,transparent);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border:1px solid var(--border);border-radius:var(--radius-xl, 14px);box-shadow:0 12px 40px color-mix(in srgb,var(--text-base) 16%,transparent);transition:opacity .1s ease-out}.ContextMenuPanel--visible{visibility:visible;opacity:1;pointer-events:auto}.ContextMenuPanel--menu{min-width:220px;max-width:min(280px,100vw - 16px);padding:4px;border-radius:var(--radius-xl, 14px);display:flex;flex-direction:column;gap:2px;animation:cupertino-menu-in .1s ease-out}.ContextMenuMenuTitle{margin:0;padding:4px 8px 2px;font-size:.7rem;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ContextMenuMenuSep{height:1px;margin:2px 4px;background:var(--border)}.ContextMenuMenuItem{display:flex;align-items:center;gap:8px;width:100%;height:32px;padding:4px 8px;border:none;border-radius:var(--radius-md, 8px);background:transparent;color:var(--text-base);font:inherit;font-size:.875rem;text-align:left;cursor:pointer;transition:background-color .12s ease,transform .1s ease}.ContextMenuMenuItem lucide-icon{flex-shrink:0;--lucide-stroke: var(--icon-base, var(--text-base))}.ContextMenuMenuItem:hover,.ContextMenuMenuItem:focus-visible{outline:none;background:var(--info-bg, var(--primary-soft, var(--surface-2)))}.ContextMenuMenuItem:active:not(:disabled){transform:scale(.98)}.ContextMenuMenuItem--danger{color:var(--error-text)}.ContextMenuMenuItem--danger lucide-icon{--lucide-stroke: var(--error-text)}.ContextMenuMenuItem--danger:hover,.ContextMenuMenuItem--danger:focus-visible{background:var(--error-bg)}.ContextMenuMenuItem--disabled,.ContextMenuMenuItem:disabled{opacity:.45;cursor:not-allowed;pointer-events:none}.ContextMenuPanel--info{padding:0}.ContextMenuInfo{padding:12px;display:flex;flex-direction:column;gap:8px}.ContextMenuInfoHeader{display:flex;align-items:center;gap:8px;color:var(--text-base)}.ContextMenuInfoHeader strong{font-size:.9rem;font-weight:600}.ContextMenuInfoHeader lucide-icon{flex-shrink:0;--lucide-stroke: var(--primary)}.ContextMenuInfoBody{margin:0;font-size:.82rem;line-height:1.45;color:var(--text-label, var(--text-muted))}@keyframes cupertino-menu-in{0%{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: LucideIconComponent, selector: "lucide-icon", inputs: ["name", "size", "spin", "filled"] }] });
558
571
  }
559
572
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: ContextMenuServiceComponent, decorators: [{
560
573
  type: Component,
561
- args: [{ selector: 'context-menu-service', standalone: true, imports: [CommonModule, LucideIconComponent], template: "@if (active) {\r\n <div class=\"ContextMenuOverlay\" aria-hidden=\"true\">\r\n <div\r\n #panel\r\n class=\"ContextMenuPanel\"\r\n [class.ContextMenuPanel--visible]=\"panelVisible\"\r\n [class.ContextMenuPanel--menu]=\"active.kind === 'menu'\"\r\n [class.ContextMenuPanel--info]=\"active.kind === 'info'\"\r\n [style.left.px]=\"position.x\"\r\n [style.top.px]=\"position.y\"\r\n role=\"menu\"\r\n [attr.aria-label]=\"active.kind === 'info' ? active.info.title : (active.title || 'Opciones')\"\r\n [attr.aria-hidden]=\"!panelVisible\"\r\n (contextmenu)=\"$event.preventDefault()\"\r\n >\r\n @if (active.kind === 'menu') {\r\n @if (active.title) {\r\n <p class=\"ContextMenuMenuTitle\">{{ active.title }}</p>\r\n }\r\n @for (option of active.options; track $index) {\r\n @if (option.separatorBefore) {\r\n <div class=\"ContextMenuMenuSep\" aria-hidden=\"true\"></div>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"ContextMenuMenuItem\"\r\n [class.ContextMenuMenuItem--danger]=\"option.danger\"\r\n [class.ContextMenuMenuItem--disabled]=\"option.disabled\"\r\n [disabled]=\"option.disabled\"\r\n role=\"menuitem\"\r\n (click)=\"selectOption(option)\"\r\n >\r\n <lucide-icon [name]=\"option.icon\" [size]=\"14\"></lucide-icon>\r\n <span>{{ option.name }}</span>\r\n </button>\r\n }\r\n } @else {\r\n <div class=\"ContextMenuInfo\">\r\n <div class=\"ContextMenuInfoHeader\">\r\n <lucide-icon [name]=\"active.info.icon\" [size]=\"18\"></lucide-icon>\r\n <strong>{{ active.info.title }}</strong>\r\n </div>\r\n <p class=\"ContextMenuInfoBody\">{{ active.info.info }}</p>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n}\r\n", styles: [".ContextMenuOverlay{position:fixed;inset:5px;pointer-events:none;z-index:10001}.ContextMenuPanel{position:fixed;pointer-events:none;visibility:hidden;opacity:0;min-width:10rem;max-width:min(18rem,100vw - 10px);max-height:calc(100vh - 10px);overflow-x:hidden;overflow-y:auto;background-color:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);box-shadow:0 8px 24px var(--shadow-color);transition:opacity .1s ease-out}.ContextMenuPanel--visible{visibility:visible;opacity:1;pointer-events:auto}.ContextMenuPanel--menu{min-width:220px;max-width:min(280px,100vw - 16px);padding:4px;border-radius:12px;border:1px solid var(--border);background:var(--surface);box-shadow:0 12px 40px var(--shadow-color),0 0 0 .5px var(--border);display:flex;flex-direction:column;gap:2px;animation:cupertino-menu-in .1s ease-out}.ContextMenuMenuTitle{margin:0;padding:4px 8px 2px;font-size:.7rem;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ContextMenuMenuSep{height:1px;margin:2px 4px;background:var(--surface-2, var(--border))}.ContextMenuMenuItem{display:flex;align-items:center;gap:8px;width:100%;padding:4px 8px;border:none;border-radius:8px;background:transparent;color:var(--text-base);font:inherit;font-size:.875rem;text-align:left;cursor:pointer}.ContextMenuMenuItem lucide-icon{flex-shrink:0;--lucide-stroke: var(--icon-base)}.ContextMenuMenuItem:hover,.ContextMenuMenuItem:focus-visible{outline:none;background:var(--info-bg, var(--primary-soft, var(--surface-2)))}.ContextMenuMenuItem--danger{color:var(--error-text)}.ContextMenuMenuItem--danger lucide-icon{--lucide-stroke: var(--error-text)}.ContextMenuMenuItem--danger:hover,.ContextMenuMenuItem--danger:focus-visible{background:var(--error-bg)}.ContextMenuMenuItem--disabled,.ContextMenuMenuItem:disabled{opacity:.45;cursor:not-allowed;pointer-events:none}.ContextMenuPanel--info{padding:0}.ContextMenuInfo{padding:12px;display:flex;flex-direction:column;gap:8px}.ContextMenuInfoHeader{display:flex;align-items:center;gap:8px;color:var(--text-base)}.ContextMenuInfoHeader strong{font-size:.9rem;font-weight:600}.ContextMenuInfoHeader lucide-icon{flex-shrink:0;--lucide-stroke: var(--primary)}.ContextMenuInfoBody{margin:0;font-size:.82rem;line-height:1.45;color:var(--text-label)}@keyframes cupertino-menu-in{0%{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}\n"] }]
574
+ args: [{ selector: 'context-menu-service', standalone: true, imports: [CommonModule, LucideIconComponent], template: "@if (active) {\r\n <div class=\"ContextMenuOverlay\" aria-hidden=\"true\">\r\n <div\r\n #panel\r\n class=\"ContextMenuPanel\"\r\n [class.ContextMenuPanel--visible]=\"panelVisible\"\r\n [class.ContextMenuPanel--menu]=\"active.kind === 'menu'\"\r\n [class.ContextMenuPanel--info]=\"active.kind === 'info'\"\r\n [style.left.px]=\"position.x\"\r\n [style.top.px]=\"position.y\"\r\n role=\"menu\"\r\n [attr.aria-label]=\"active.kind === 'info' ? active.info.title : (active.title || 'Opciones')\"\r\n [attr.aria-hidden]=\"!panelVisible\"\r\n (contextmenu)=\"$event.preventDefault()\"\r\n >\r\n @if (active.kind === 'menu') {\r\n @if (active.title) {\r\n <p class=\"ContextMenuMenuTitle\">{{ active.title }}</p>\r\n }\r\n @for (option of active.options; track $index) {\r\n @if (option.separatorBefore) {\r\n <div class=\"ContextMenuMenuSep\" aria-hidden=\"true\"></div>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"ContextMenuMenuItem\"\r\n [class.ContextMenuMenuItem--danger]=\"option.danger\"\r\n [class.ContextMenuMenuItem--disabled]=\"option.disabled\"\r\n [disabled]=\"option.disabled\"\r\n role=\"menuitem\"\r\n (click)=\"selectOption(option)\"\r\n >\r\n <lucide-icon [name]=\"option.icon\" [size]=\"14\"></lucide-icon>\r\n <span>{{ option.name }}</span>\r\n </button>\r\n }\r\n } @else {\r\n <div class=\"ContextMenuInfo\">\r\n <div class=\"ContextMenuInfoHeader\">\r\n <lucide-icon [name]=\"active.info.icon\" [size]=\"18\"></lucide-icon>\r\n <strong>{{ active.info.title }}</strong>\r\n </div>\r\n <p class=\"ContextMenuInfoBody\">{{ active.info.info }}</p>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n}\r\n", styles: [".ContextMenuOverlay{position:fixed;inset:5px;pointer-events:none;z-index:10001}.ContextMenuPanel{position:fixed;pointer-events:none;visibility:hidden;opacity:0;min-width:10rem;max-width:min(18rem,100vw - 10px);max-height:calc(100vh - 10px);overflow-x:hidden;overflow-y:auto;background-color:color-mix(in srgb,var(--surface) 88%,transparent);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border:1px solid var(--border);border-radius:var(--radius-xl, 14px);box-shadow:0 12px 40px color-mix(in srgb,var(--text-base) 16%,transparent);transition:opacity .1s ease-out}.ContextMenuPanel--visible{visibility:visible;opacity:1;pointer-events:auto}.ContextMenuPanel--menu{min-width:220px;max-width:min(280px,100vw - 16px);padding:4px;border-radius:var(--radius-xl, 14px);display:flex;flex-direction:column;gap:2px;animation:cupertino-menu-in .1s ease-out}.ContextMenuMenuTitle{margin:0;padding:4px 8px 2px;font-size:.7rem;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ContextMenuMenuSep{height:1px;margin:2px 4px;background:var(--border)}.ContextMenuMenuItem{display:flex;align-items:center;gap:8px;width:100%;height:32px;padding:4px 8px;border:none;border-radius:var(--radius-md, 8px);background:transparent;color:var(--text-base);font:inherit;font-size:.875rem;text-align:left;cursor:pointer;transition:background-color .12s ease,transform .1s ease}.ContextMenuMenuItem lucide-icon{flex-shrink:0;--lucide-stroke: var(--icon-base, var(--text-base))}.ContextMenuMenuItem:hover,.ContextMenuMenuItem:focus-visible{outline:none;background:var(--info-bg, var(--primary-soft, var(--surface-2)))}.ContextMenuMenuItem:active:not(:disabled){transform:scale(.98)}.ContextMenuMenuItem--danger{color:var(--error-text)}.ContextMenuMenuItem--danger lucide-icon{--lucide-stroke: var(--error-text)}.ContextMenuMenuItem--danger:hover,.ContextMenuMenuItem--danger:focus-visible{background:var(--error-bg)}.ContextMenuMenuItem--disabled,.ContextMenuMenuItem:disabled{opacity:.45;cursor:not-allowed;pointer-events:none}.ContextMenuPanel--info{padding:0}.ContextMenuInfo{padding:12px;display:flex;flex-direction:column;gap:8px}.ContextMenuInfoHeader{display:flex;align-items:center;gap:8px;color:var(--text-base)}.ContextMenuInfoHeader strong{font-size:.9rem;font-weight:600}.ContextMenuInfoHeader lucide-icon{flex-shrink:0;--lucide-stroke: var(--primary)}.ContextMenuInfoBody{margin:0;font-size:.82rem;line-height:1.45;color:var(--text-label, var(--text-muted))}@keyframes cupertino-menu-in{0%{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}\n"] }]
562
575
  }], ctorParameters: () => [{ type: ContextMenuService }, { type: undefined, decorators: [{
563
576
  type: Inject,
564
577
  args: [PLATFORM_ID]
@@ -1117,7 +1130,7 @@ class MediaViewerServiceComponent {
1117
1130
  this.cdr.markForCheck();
1118
1131
  }
1119
1132
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: MediaViewerServiceComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1120
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.11", type: MediaViewerServiceComponent, isStandalone: true, selector: "media-viewer-service", ngImport: i0, template: "<div class=\"GeneralViewerBackdrop\" *ngIf=\"open\" (click)=\"onBackdropClick()\">\r\n <div class=\"GeneralViewerContent\" (click)=\"onContentClick($event)\">\r\n <div class=\"GeneralViewerHeader\">\r\n <span class=\"GeneralViewerTypeLabel\">{{ typeLabel }}</span>\r\n <button type=\"button\" class=\"GeneralViewerClose\" (click)=\"close()\" aria-label=\"Cerrar\">\r\n <lucide-icon name=\"x\"></lucide-icon>\r\n </button>\r\n </div>\r\n <div class=\"GeneralViewerBody\" [class.GeneralViewerBody--document]=\"effectiveMode === 'document'\">\r\n <div *ngIf=\"detectingOrLoading\" class=\"GeneralViewerNoContent\">\r\n <lucide-icon name=\"loader-circle\" [spin]=\"true\"></lucide-icon>\r\n <p>{{ loadingMessage }}</p>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">Cerrar</button>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && detectError && !resolvedMediaUrl && effectiveMode === 'download'\" class=\"GeneralViewerNoContent\">\r\n <lucide-icon name=\"triangle-alert\"></lucide-icon>\r\n <p>No se pudo detectar el tipo de archivo.</p>\r\n <p class=\"GeneralViewerFileName\">{{ fileName }}</p>\r\n <button *ngIf=\"url\" type=\"button\" class=\"GeneralViewerBtnDownload\" (click)=\"triggerDownload()\">Descargar</button>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">Cerrar</button>\r\n </div>\r\n\r\n <app-image-viewer\r\n *ngIf=\"!detectingOrLoading && effectiveMode === 'image' && mediaSrc\"\r\n [src]=\"mediaSrc\"\r\n ></app-image-viewer>\r\n\r\n <app-video-player\r\n *ngIf=\"!detectingOrLoading && effectiveMode === 'video' && mediaSrc\"\r\n [src]=\"mediaSrc\"\r\n [videoFormat]=\"videoPlayerFormat\"\r\n ></app-video-player>\r\n\r\n <app-audio-player\r\n *ngIf=\"!detectingOrLoading && effectiveMode === 'audio' && mediaSrc\"\r\n [src]=\"mediaSrc\"\r\n [name]=\"fileName\"\r\n ></app-audio-player>\r\n\r\n <div *ngIf=\"!detectingOrLoading && effectiveMode === 'document' && documentUrlSafe && !documentRenderFailed\" class=\"GeneralViewerDocument\">\r\n <iframe\r\n [src]=\"documentUrlSafe\"\r\n frameborder=\"0\"\r\n class=\"GeneralViewerIframe\"\r\n [attr.title]=\"fileName\"\r\n (load)=\"onDocumentIframeLoad()\"\r\n ></iframe>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && effectiveMode === 'document' && documentRenderFailed\" class=\"GeneralViewerDownload\">\r\n <p class=\"GeneralViewerDownloadMessage\">\r\n No se pudo mostrar el documento. Puede descargarlo con el bot\u00F3n o cerrar.\r\n </p>\r\n <p class=\"GeneralViewerDownloadFileName\">{{ fileName }}</p>\r\n <button *ngIf=\"downloadUrl || url || downloadContext\" type=\"button\" class=\"GeneralViewerBtnDownload\" (click)=\"triggerDownload()\">\r\n Descargar\r\n </button>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">\r\n Listo, Cerrar\r\n </button>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && effectiveMode === 'download' && !detectError\" class=\"GeneralViewerDownload\">\r\n <p class=\"GeneralViewerDownloadMessage\">\r\n {{ (downloadUrl || url || downloadContext) ? 'Este archivo no se puede previsualizar. Puede descargarlo con el bot\u00F3n o cerrar.' : 'No se pudo cargar la URL del archivo.' }}\r\n </p>\r\n <p class=\"GeneralViewerDownloadFileName\">{{ fileName }}</p>\r\n <button *ngIf=\"downloadUrl || url || downloadContext\" type=\"button\" class=\"GeneralViewerBtnDownload\" (click)=\"triggerDownload()\">\r\n Descargar\r\n </button>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">\r\n Listo, Cerrar\r\n </button>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && open && effectiveMode === 'document' && !documentUrlSafe && !documentRenderFailed\" class=\"GeneralViewerNoContent\">\r\n <lucide-icon name=\"loader-circle\" [spin]=\"true\"></lucide-icon>\r\n <p>Cargando documento...</p>\r\n <p class=\"GeneralViewerFileName\">{{ fileName }}</p>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">Cerrar</button>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && open && effectiveMode !== 'download' && effectiveMode !== 'document' && !mediaSrc && !documentUrlSafe\" class=\"GeneralViewerNoContent\">\r\n <lucide-icon name=\"file\"></lucide-icon>\r\n <p>No se pudo cargar el archivo</p>\r\n <p class=\"GeneralViewerFileName\">{{ fileName }}</p>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">Listo, Cerrar</button>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".GeneralViewerBackdrop{position:fixed;top:0;left:0;width:100%;height:100%;background-color:color-mix(in srgb,var(--text-base) 50%,transparent);display:flex;align-items:center;justify-content:center;z-index:2000;padding:0;overflow:hidden}.GeneralViewerContent{position:relative;background-color:var(--media-viewer-bg, var(--surface-inverse, var(--text-base)));width:100%;height:100%;max-width:100vw;max-height:100vh;display:flex;flex-direction:column;overflow:hidden}.GeneralViewerHeader{position:absolute;top:0;left:0;z-index:1;width:100%;height:48px;display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:linear-gradient(to bottom,color-mix(in srgb,var(--text-base) 60%,transparent),transparent)}.GeneralViewerTypeLabel{margin-right:12px;padding:4px 8px;border-radius:var(--radius-md);font-size:.875rem;font-weight:600;color:var(--text-inverted);background-color:color-mix(in srgb,var(--text-base) 40%,transparent)}.GeneralViewerFileName{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-right:12px;padding:4px 8px;border-radius:var(--radius-md);font-size:.875rem;color:var(--text-inverted);max-width:70%;background-color:color-mix(in srgb,var(--text-base) 40%,transparent)}.GeneralViewerClose{width:40px;height:40px;display:flex;align-items:center;justify-content:center;border:none;border-radius:50%;cursor:pointer;font-size:1.25rem;color:var(--text-inverted);background-color:color-mix(in srgb,var(--text-base) 40%,transparent);flex-shrink:0}.GeneralViewerClose:hover{background-color:color-mix(in srgb,var(--text-inverted) 15%,transparent)}.GeneralViewerBody{flex:1;display:flex;align-items:center;justify-content:center;overflow:hidden;overflow-y:auto;min-height:0;background-color:var(--media-viewer-body-bg, var(--surface-inverse, var(--text-base)))}.GeneralViewerBody.GeneralViewerBody--document{padding-top:48px;align-items:flex-start;justify-content:center}.GeneralViewerBody app-image-viewer,.GeneralViewerBody app-video-player{width:100%;height:100%;min-height:200px}.GeneralViewerBody app-audio-player{width:100%;max-width:560px;padding:16px}.GeneralViewerNoContent,.GeneralViewerDownload{display:flex;flex-direction:column;align-items:center;gap:8px;padding:16px;color:var(--text-inverted);text-align:center}.GeneralViewerDocument{width:100%;height:100%;min-height:0}.GeneralViewerIframe{width:100%;height:100%;border:none;background:var(--surface)}.GeneralViewerDownloadMessage,.GeneralViewerDownloadFileName{margin:0;color:var(--text-inverted)}.GeneralViewerBtnDownload,.GeneralViewerBtnClose{padding:8px 16px;border:none;border-radius:var(--radius-md);font-size:.875rem;font-weight:500;cursor:pointer}.GeneralViewerBtnDownload{background-color:var(--primary);color:var(--text-inverted)}.GeneralViewerBtnClose{background-color:color-mix(in srgb,var(--text-inverted) 20%,transparent);color:var(--text-inverted)}\n"], dependencies: [{ kind: "component", type: LucideIconComponent, selector: "lucide-icon", inputs: ["name", "size", "spin", "filled"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ImageViewerComponent, selector: "app-image-viewer", inputs: ["src"] }, { kind: "component", type: VideoPlayerComponent, selector: "app-video-player", inputs: ["src", "videoFormat"] }, { kind: "component", type: AudioPlayerComponent, selector: "app-audio-player", inputs: ["src", "name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1133
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.11", type: MediaViewerServiceComponent, isStandalone: true, selector: "media-viewer-service", ngImport: i0, template: "<div class=\"GeneralViewerBackdrop\" *ngIf=\"open\" (click)=\"onBackdropClick()\">\r\n <div class=\"GeneralViewerContent\" (click)=\"onContentClick($event)\">\r\n <div class=\"GeneralViewerHeader\">\r\n <span class=\"GeneralViewerTypeLabel\">{{ typeLabel }}</span>\r\n <button type=\"button\" class=\"GeneralViewerClose\" (click)=\"close()\" aria-label=\"Cerrar\">\r\n <lucide-icon name=\"x\"></lucide-icon>\r\n </button>\r\n </div>\r\n <div class=\"GeneralViewerBody\" [class.GeneralViewerBody--document]=\"effectiveMode === 'document'\">\r\n <div *ngIf=\"detectingOrLoading\" class=\"GeneralViewerNoContent\">\r\n <lucide-icon name=\"loader-circle\" [spin]=\"true\"></lucide-icon>\r\n <p>{{ loadingMessage }}</p>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">Cerrar</button>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && detectError && !resolvedMediaUrl && effectiveMode === 'download'\" class=\"GeneralViewerNoContent\">\r\n <lucide-icon name=\"triangle-alert\"></lucide-icon>\r\n <p>No se pudo detectar el tipo de archivo.</p>\r\n <p class=\"GeneralViewerFileName\">{{ fileName }}</p>\r\n <button *ngIf=\"url\" type=\"button\" class=\"GeneralViewerBtnDownload\" (click)=\"triggerDownload()\">Descargar</button>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">Cerrar</button>\r\n </div>\r\n\r\n <app-image-viewer\r\n *ngIf=\"!detectingOrLoading && effectiveMode === 'image' && mediaSrc\"\r\n [src]=\"mediaSrc\"\r\n ></app-image-viewer>\r\n\r\n <app-video-player\r\n *ngIf=\"!detectingOrLoading && effectiveMode === 'video' && mediaSrc\"\r\n [src]=\"mediaSrc\"\r\n [videoFormat]=\"videoPlayerFormat\"\r\n ></app-video-player>\r\n\r\n <app-audio-player\r\n *ngIf=\"!detectingOrLoading && effectiveMode === 'audio' && mediaSrc\"\r\n [src]=\"mediaSrc\"\r\n [name]=\"fileName\"\r\n ></app-audio-player>\r\n\r\n <div *ngIf=\"!detectingOrLoading && effectiveMode === 'document' && documentUrlSafe && !documentRenderFailed\" class=\"GeneralViewerDocument\">\r\n <iframe\r\n [src]=\"documentUrlSafe\"\r\n frameborder=\"0\"\r\n class=\"GeneralViewerIframe\"\r\n [attr.title]=\"fileName\"\r\n (load)=\"onDocumentIframeLoad()\"\r\n ></iframe>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && effectiveMode === 'document' && documentRenderFailed\" class=\"GeneralViewerDownload\">\r\n <p class=\"GeneralViewerDownloadMessage\">\r\n No se pudo mostrar el documento. Puede descargarlo con el bot\u00F3n o cerrar.\r\n </p>\r\n <p class=\"GeneralViewerDownloadFileName\">{{ fileName }}</p>\r\n <button *ngIf=\"downloadUrl || url || downloadContext\" type=\"button\" class=\"GeneralViewerBtnDownload\" (click)=\"triggerDownload()\">\r\n Descargar\r\n </button>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">\r\n Listo, Cerrar\r\n </button>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && effectiveMode === 'download' && !detectError\" class=\"GeneralViewerDownload\">\r\n <p class=\"GeneralViewerDownloadMessage\">\r\n {{ (downloadUrl || url || downloadContext) ? 'Este archivo no se puede previsualizar. Puede descargarlo con el bot\u00F3n o cerrar.' : 'No se pudo cargar la URL del archivo.' }}\r\n </p>\r\n <p class=\"GeneralViewerDownloadFileName\">{{ fileName }}</p>\r\n <button *ngIf=\"downloadUrl || url || downloadContext\" type=\"button\" class=\"GeneralViewerBtnDownload\" (click)=\"triggerDownload()\">\r\n Descargar\r\n </button>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">\r\n Listo, Cerrar\r\n </button>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && open && effectiveMode === 'document' && !documentUrlSafe && !documentRenderFailed\" class=\"GeneralViewerNoContent\">\r\n <lucide-icon name=\"loader-circle\" [spin]=\"true\"></lucide-icon>\r\n <p>Cargando documento...</p>\r\n <p class=\"GeneralViewerFileName\">{{ fileName }}</p>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">Cerrar</button>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && open && effectiveMode !== 'download' && effectiveMode !== 'document' && !mediaSrc && !documentUrlSafe\" class=\"GeneralViewerNoContent\">\r\n <lucide-icon name=\"file\"></lucide-icon>\r\n <p>No se pudo cargar el archivo</p>\r\n <p class=\"GeneralViewerFileName\">{{ fileName }}</p>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">Listo, Cerrar</button>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".GeneralViewerBackdrop{position:fixed;top:0;left:0;width:100%;height:100%;background-color:var(--overlay-scrim, color-mix(in srgb, var(--text-base) 55%, transparent));backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;z-index:2000;padding:0;overflow:hidden}.GeneralViewerContent{position:relative;background-color:var(--media-viewer-bg, var(--surface-inverse, var(--text-base)));width:100%;height:100%;max-width:100vw;max-height:100vh;display:flex;flex-direction:column;overflow:hidden}.GeneralViewerHeader{position:absolute;top:0;left:0;z-index:1;width:100%;height:48px;display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:linear-gradient(to bottom,color-mix(in srgb,var(--text-base) 60%,transparent),transparent)}.GeneralViewerTypeLabel{margin-right:12px;padding:4px 8px;border-radius:var(--radius-md);font-size:.875rem;font-weight:600;color:var(--text-inverted);background-color:color-mix(in srgb,var(--text-base) 40%,transparent)}.GeneralViewerFileName{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-right:12px;padding:4px 8px;border-radius:var(--radius-md);font-size:.875rem;color:var(--text-inverted);max-width:70%;background-color:color-mix(in srgb,var(--text-base) 40%,transparent)}.GeneralViewerClose{width:40px;height:40px;display:flex;align-items:center;justify-content:center;border:none;border-radius:50%;cursor:pointer;font-size:1.25rem;color:var(--text-inverted);background-color:color-mix(in srgb,var(--text-base) 40%,transparent);flex-shrink:0}.GeneralViewerClose:hover{background-color:color-mix(in srgb,var(--text-inverted) 15%,transparent)}.GeneralViewerBody{flex:1;display:flex;align-items:center;justify-content:center;overflow:hidden;overflow-y:auto;min-height:0;background-color:var(--media-viewer-body-bg, var(--surface-inverse, var(--text-base)))}.GeneralViewerBody.GeneralViewerBody--document{padding-top:48px;align-items:flex-start;justify-content:center}.GeneralViewerBody app-image-viewer,.GeneralViewerBody app-video-player{width:100%;height:100%;min-height:200px}.GeneralViewerBody app-audio-player{width:100%;max-width:560px;padding:16px}.GeneralViewerNoContent,.GeneralViewerDownload{display:flex;flex-direction:column;align-items:center;gap:8px;padding:16px;color:var(--text-inverted);text-align:center}.GeneralViewerDocument{width:100%;height:100%;min-height:0}.GeneralViewerIframe{width:100%;height:100%;border:none;background:var(--surface)}.GeneralViewerDownloadMessage,.GeneralViewerDownloadFileName{margin:0;color:var(--text-inverted)}.GeneralViewerBtnDownload,.GeneralViewerBtnClose{padding:8px 16px;border:none;border-radius:var(--radius-md);font-size:.875rem;font-weight:500;cursor:pointer}.GeneralViewerBtnDownload{background-color:var(--primary);color:var(--text-inverted)}.GeneralViewerBtnClose{background-color:color-mix(in srgb,var(--text-inverted) 20%,transparent);color:var(--text-inverted)}\n"], dependencies: [{ kind: "component", type: LucideIconComponent, selector: "lucide-icon", inputs: ["name", "size", "spin", "filled"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ImageViewerComponent, selector: "app-image-viewer", inputs: ["src"] }, { kind: "component", type: VideoPlayerComponent, selector: "app-video-player", inputs: ["src", "videoFormat"] }, { kind: "component", type: AudioPlayerComponent, selector: "app-audio-player", inputs: ["src", "name"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1121
1134
  }
1122
1135
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: MediaViewerServiceComponent, decorators: [{
1123
1136
  type: Component,
@@ -1127,7 +1140,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImpo
1127
1140
  ImageViewerComponent,
1128
1141
  VideoPlayerComponent,
1129
1142
  AudioPlayerComponent,
1130
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"GeneralViewerBackdrop\" *ngIf=\"open\" (click)=\"onBackdropClick()\">\r\n <div class=\"GeneralViewerContent\" (click)=\"onContentClick($event)\">\r\n <div class=\"GeneralViewerHeader\">\r\n <span class=\"GeneralViewerTypeLabel\">{{ typeLabel }}</span>\r\n <button type=\"button\" class=\"GeneralViewerClose\" (click)=\"close()\" aria-label=\"Cerrar\">\r\n <lucide-icon name=\"x\"></lucide-icon>\r\n </button>\r\n </div>\r\n <div class=\"GeneralViewerBody\" [class.GeneralViewerBody--document]=\"effectiveMode === 'document'\">\r\n <div *ngIf=\"detectingOrLoading\" class=\"GeneralViewerNoContent\">\r\n <lucide-icon name=\"loader-circle\" [spin]=\"true\"></lucide-icon>\r\n <p>{{ loadingMessage }}</p>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">Cerrar</button>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && detectError && !resolvedMediaUrl && effectiveMode === 'download'\" class=\"GeneralViewerNoContent\">\r\n <lucide-icon name=\"triangle-alert\"></lucide-icon>\r\n <p>No se pudo detectar el tipo de archivo.</p>\r\n <p class=\"GeneralViewerFileName\">{{ fileName }}</p>\r\n <button *ngIf=\"url\" type=\"button\" class=\"GeneralViewerBtnDownload\" (click)=\"triggerDownload()\">Descargar</button>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">Cerrar</button>\r\n </div>\r\n\r\n <app-image-viewer\r\n *ngIf=\"!detectingOrLoading && effectiveMode === 'image' && mediaSrc\"\r\n [src]=\"mediaSrc\"\r\n ></app-image-viewer>\r\n\r\n <app-video-player\r\n *ngIf=\"!detectingOrLoading && effectiveMode === 'video' && mediaSrc\"\r\n [src]=\"mediaSrc\"\r\n [videoFormat]=\"videoPlayerFormat\"\r\n ></app-video-player>\r\n\r\n <app-audio-player\r\n *ngIf=\"!detectingOrLoading && effectiveMode === 'audio' && mediaSrc\"\r\n [src]=\"mediaSrc\"\r\n [name]=\"fileName\"\r\n ></app-audio-player>\r\n\r\n <div *ngIf=\"!detectingOrLoading && effectiveMode === 'document' && documentUrlSafe && !documentRenderFailed\" class=\"GeneralViewerDocument\">\r\n <iframe\r\n [src]=\"documentUrlSafe\"\r\n frameborder=\"0\"\r\n class=\"GeneralViewerIframe\"\r\n [attr.title]=\"fileName\"\r\n (load)=\"onDocumentIframeLoad()\"\r\n ></iframe>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && effectiveMode === 'document' && documentRenderFailed\" class=\"GeneralViewerDownload\">\r\n <p class=\"GeneralViewerDownloadMessage\">\r\n No se pudo mostrar el documento. Puede descargarlo con el bot\u00F3n o cerrar.\r\n </p>\r\n <p class=\"GeneralViewerDownloadFileName\">{{ fileName }}</p>\r\n <button *ngIf=\"downloadUrl || url || downloadContext\" type=\"button\" class=\"GeneralViewerBtnDownload\" (click)=\"triggerDownload()\">\r\n Descargar\r\n </button>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">\r\n Listo, Cerrar\r\n </button>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && effectiveMode === 'download' && !detectError\" class=\"GeneralViewerDownload\">\r\n <p class=\"GeneralViewerDownloadMessage\">\r\n {{ (downloadUrl || url || downloadContext) ? 'Este archivo no se puede previsualizar. Puede descargarlo con el bot\u00F3n o cerrar.' : 'No se pudo cargar la URL del archivo.' }}\r\n </p>\r\n <p class=\"GeneralViewerDownloadFileName\">{{ fileName }}</p>\r\n <button *ngIf=\"downloadUrl || url || downloadContext\" type=\"button\" class=\"GeneralViewerBtnDownload\" (click)=\"triggerDownload()\">\r\n Descargar\r\n </button>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">\r\n Listo, Cerrar\r\n </button>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && open && effectiveMode === 'document' && !documentUrlSafe && !documentRenderFailed\" class=\"GeneralViewerNoContent\">\r\n <lucide-icon name=\"loader-circle\" [spin]=\"true\"></lucide-icon>\r\n <p>Cargando documento...</p>\r\n <p class=\"GeneralViewerFileName\">{{ fileName }}</p>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">Cerrar</button>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && open && effectiveMode !== 'download' && effectiveMode !== 'document' && !mediaSrc && !documentUrlSafe\" class=\"GeneralViewerNoContent\">\r\n <lucide-icon name=\"file\"></lucide-icon>\r\n <p>No se pudo cargar el archivo</p>\r\n <p class=\"GeneralViewerFileName\">{{ fileName }}</p>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">Listo, Cerrar</button>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".GeneralViewerBackdrop{position:fixed;top:0;left:0;width:100%;height:100%;background-color:color-mix(in srgb,var(--text-base) 50%,transparent);display:flex;align-items:center;justify-content:center;z-index:2000;padding:0;overflow:hidden}.GeneralViewerContent{position:relative;background-color:var(--media-viewer-bg, var(--surface-inverse, var(--text-base)));width:100%;height:100%;max-width:100vw;max-height:100vh;display:flex;flex-direction:column;overflow:hidden}.GeneralViewerHeader{position:absolute;top:0;left:0;z-index:1;width:100%;height:48px;display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:linear-gradient(to bottom,color-mix(in srgb,var(--text-base) 60%,transparent),transparent)}.GeneralViewerTypeLabel{margin-right:12px;padding:4px 8px;border-radius:var(--radius-md);font-size:.875rem;font-weight:600;color:var(--text-inverted);background-color:color-mix(in srgb,var(--text-base) 40%,transparent)}.GeneralViewerFileName{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-right:12px;padding:4px 8px;border-radius:var(--radius-md);font-size:.875rem;color:var(--text-inverted);max-width:70%;background-color:color-mix(in srgb,var(--text-base) 40%,transparent)}.GeneralViewerClose{width:40px;height:40px;display:flex;align-items:center;justify-content:center;border:none;border-radius:50%;cursor:pointer;font-size:1.25rem;color:var(--text-inverted);background-color:color-mix(in srgb,var(--text-base) 40%,transparent);flex-shrink:0}.GeneralViewerClose:hover{background-color:color-mix(in srgb,var(--text-inverted) 15%,transparent)}.GeneralViewerBody{flex:1;display:flex;align-items:center;justify-content:center;overflow:hidden;overflow-y:auto;min-height:0;background-color:var(--media-viewer-body-bg, var(--surface-inverse, var(--text-base)))}.GeneralViewerBody.GeneralViewerBody--document{padding-top:48px;align-items:flex-start;justify-content:center}.GeneralViewerBody app-image-viewer,.GeneralViewerBody app-video-player{width:100%;height:100%;min-height:200px}.GeneralViewerBody app-audio-player{width:100%;max-width:560px;padding:16px}.GeneralViewerNoContent,.GeneralViewerDownload{display:flex;flex-direction:column;align-items:center;gap:8px;padding:16px;color:var(--text-inverted);text-align:center}.GeneralViewerDocument{width:100%;height:100%;min-height:0}.GeneralViewerIframe{width:100%;height:100%;border:none;background:var(--surface)}.GeneralViewerDownloadMessage,.GeneralViewerDownloadFileName{margin:0;color:var(--text-inverted)}.GeneralViewerBtnDownload,.GeneralViewerBtnClose{padding:8px 16px;border:none;border-radius:var(--radius-md);font-size:.875rem;font-weight:500;cursor:pointer}.GeneralViewerBtnDownload{background-color:var(--primary);color:var(--text-inverted)}.GeneralViewerBtnClose{background-color:color-mix(in srgb,var(--text-inverted) 20%,transparent);color:var(--text-inverted)}\n"] }]
1143
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"GeneralViewerBackdrop\" *ngIf=\"open\" (click)=\"onBackdropClick()\">\r\n <div class=\"GeneralViewerContent\" (click)=\"onContentClick($event)\">\r\n <div class=\"GeneralViewerHeader\">\r\n <span class=\"GeneralViewerTypeLabel\">{{ typeLabel }}</span>\r\n <button type=\"button\" class=\"GeneralViewerClose\" (click)=\"close()\" aria-label=\"Cerrar\">\r\n <lucide-icon name=\"x\"></lucide-icon>\r\n </button>\r\n </div>\r\n <div class=\"GeneralViewerBody\" [class.GeneralViewerBody--document]=\"effectiveMode === 'document'\">\r\n <div *ngIf=\"detectingOrLoading\" class=\"GeneralViewerNoContent\">\r\n <lucide-icon name=\"loader-circle\" [spin]=\"true\"></lucide-icon>\r\n <p>{{ loadingMessage }}</p>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">Cerrar</button>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && detectError && !resolvedMediaUrl && effectiveMode === 'download'\" class=\"GeneralViewerNoContent\">\r\n <lucide-icon name=\"triangle-alert\"></lucide-icon>\r\n <p>No se pudo detectar el tipo de archivo.</p>\r\n <p class=\"GeneralViewerFileName\">{{ fileName }}</p>\r\n <button *ngIf=\"url\" type=\"button\" class=\"GeneralViewerBtnDownload\" (click)=\"triggerDownload()\">Descargar</button>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">Cerrar</button>\r\n </div>\r\n\r\n <app-image-viewer\r\n *ngIf=\"!detectingOrLoading && effectiveMode === 'image' && mediaSrc\"\r\n [src]=\"mediaSrc\"\r\n ></app-image-viewer>\r\n\r\n <app-video-player\r\n *ngIf=\"!detectingOrLoading && effectiveMode === 'video' && mediaSrc\"\r\n [src]=\"mediaSrc\"\r\n [videoFormat]=\"videoPlayerFormat\"\r\n ></app-video-player>\r\n\r\n <app-audio-player\r\n *ngIf=\"!detectingOrLoading && effectiveMode === 'audio' && mediaSrc\"\r\n [src]=\"mediaSrc\"\r\n [name]=\"fileName\"\r\n ></app-audio-player>\r\n\r\n <div *ngIf=\"!detectingOrLoading && effectiveMode === 'document' && documentUrlSafe && !documentRenderFailed\" class=\"GeneralViewerDocument\">\r\n <iframe\r\n [src]=\"documentUrlSafe\"\r\n frameborder=\"0\"\r\n class=\"GeneralViewerIframe\"\r\n [attr.title]=\"fileName\"\r\n (load)=\"onDocumentIframeLoad()\"\r\n ></iframe>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && effectiveMode === 'document' && documentRenderFailed\" class=\"GeneralViewerDownload\">\r\n <p class=\"GeneralViewerDownloadMessage\">\r\n No se pudo mostrar el documento. Puede descargarlo con el bot\u00F3n o cerrar.\r\n </p>\r\n <p class=\"GeneralViewerDownloadFileName\">{{ fileName }}</p>\r\n <button *ngIf=\"downloadUrl || url || downloadContext\" type=\"button\" class=\"GeneralViewerBtnDownload\" (click)=\"triggerDownload()\">\r\n Descargar\r\n </button>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">\r\n Listo, Cerrar\r\n </button>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && effectiveMode === 'download' && !detectError\" class=\"GeneralViewerDownload\">\r\n <p class=\"GeneralViewerDownloadMessage\">\r\n {{ (downloadUrl || url || downloadContext) ? 'Este archivo no se puede previsualizar. Puede descargarlo con el bot\u00F3n o cerrar.' : 'No se pudo cargar la URL del archivo.' }}\r\n </p>\r\n <p class=\"GeneralViewerDownloadFileName\">{{ fileName }}</p>\r\n <button *ngIf=\"downloadUrl || url || downloadContext\" type=\"button\" class=\"GeneralViewerBtnDownload\" (click)=\"triggerDownload()\">\r\n Descargar\r\n </button>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">\r\n Listo, Cerrar\r\n </button>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && open && effectiveMode === 'document' && !documentUrlSafe && !documentRenderFailed\" class=\"GeneralViewerNoContent\">\r\n <lucide-icon name=\"loader-circle\" [spin]=\"true\"></lucide-icon>\r\n <p>Cargando documento...</p>\r\n <p class=\"GeneralViewerFileName\">{{ fileName }}</p>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">Cerrar</button>\r\n </div>\r\n\r\n <div *ngIf=\"!detectingOrLoading && open && effectiveMode !== 'download' && effectiveMode !== 'document' && !mediaSrc && !documentUrlSafe\" class=\"GeneralViewerNoContent\">\r\n <lucide-icon name=\"file\"></lucide-icon>\r\n <p>No se pudo cargar el archivo</p>\r\n <p class=\"GeneralViewerFileName\">{{ fileName }}</p>\r\n <button type=\"button\" class=\"GeneralViewerBtnClose\" (click)=\"close()\">Listo, Cerrar</button>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".GeneralViewerBackdrop{position:fixed;top:0;left:0;width:100%;height:100%;background-color:var(--overlay-scrim, color-mix(in srgb, var(--text-base) 55%, transparent));backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);display:flex;align-items:center;justify-content:center;z-index:2000;padding:0;overflow:hidden}.GeneralViewerContent{position:relative;background-color:var(--media-viewer-bg, var(--surface-inverse, var(--text-base)));width:100%;height:100%;max-width:100vw;max-height:100vh;display:flex;flex-direction:column;overflow:hidden}.GeneralViewerHeader{position:absolute;top:0;left:0;z-index:1;width:100%;height:48px;display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:linear-gradient(to bottom,color-mix(in srgb,var(--text-base) 60%,transparent),transparent)}.GeneralViewerTypeLabel{margin-right:12px;padding:4px 8px;border-radius:var(--radius-md);font-size:.875rem;font-weight:600;color:var(--text-inverted);background-color:color-mix(in srgb,var(--text-base) 40%,transparent)}.GeneralViewerFileName{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-right:12px;padding:4px 8px;border-radius:var(--radius-md);font-size:.875rem;color:var(--text-inverted);max-width:70%;background-color:color-mix(in srgb,var(--text-base) 40%,transparent)}.GeneralViewerClose{width:40px;height:40px;display:flex;align-items:center;justify-content:center;border:none;border-radius:50%;cursor:pointer;font-size:1.25rem;color:var(--text-inverted);background-color:color-mix(in srgb,var(--text-base) 40%,transparent);flex-shrink:0}.GeneralViewerClose:hover{background-color:color-mix(in srgb,var(--text-inverted) 15%,transparent)}.GeneralViewerBody{flex:1;display:flex;align-items:center;justify-content:center;overflow:hidden;overflow-y:auto;min-height:0;background-color:var(--media-viewer-body-bg, var(--surface-inverse, var(--text-base)))}.GeneralViewerBody.GeneralViewerBody--document{padding-top:48px;align-items:flex-start;justify-content:center}.GeneralViewerBody app-image-viewer,.GeneralViewerBody app-video-player{width:100%;height:100%;min-height:200px}.GeneralViewerBody app-audio-player{width:100%;max-width:560px;padding:16px}.GeneralViewerNoContent,.GeneralViewerDownload{display:flex;flex-direction:column;align-items:center;gap:8px;padding:16px;color:var(--text-inverted);text-align:center}.GeneralViewerDocument{width:100%;height:100%;min-height:0}.GeneralViewerIframe{width:100%;height:100%;border:none;background:var(--surface)}.GeneralViewerDownloadMessage,.GeneralViewerDownloadFileName{margin:0;color:var(--text-inverted)}.GeneralViewerBtnDownload,.GeneralViewerBtnClose{padding:8px 16px;border:none;border-radius:var(--radius-md);font-size:.875rem;font-weight:500;cursor:pointer}.GeneralViewerBtnDownload{background-color:var(--primary);color:var(--text-inverted)}.GeneralViewerBtnClose{background-color:color-mix(in srgb,var(--text-inverted) 20%,transparent);color:var(--text-inverted)}\n"] }]
1131
1144
  }], ctorParameters: () => [] });
1132
1145
 
1133
1146
  class SessionRequestService {
@@ -1184,7 +1197,7 @@ class SessionRequestServiceComponent {
1184
1197
  return item.id;
1185
1198
  }
1186
1199
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: SessionRequestServiceComponent, deps: [{ token: SessionRequestService }], target: i0.ɵɵFactoryTarget.Component });
1187
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.11", type: SessionRequestServiceComponent, isStandalone: true, selector: "session-request-service", ngImport: i0, template: "<div class=\"SessionRequestOverlay\" *ngIf=\"sessionRequests.length > 0\">\r\n <div\r\n class=\"SessionRequest\"\r\n *ngFor=\"let sessionRequest of sessionRequests; trackBy: trackById\"\r\n [@sessionRequestAnimation]\r\n >\r\n <button\r\n type=\"button\"\r\n class=\"CloseButton\"\r\n (click)=\"closeSessionRequest(sessionRequest.id)\"\r\n title=\"Cerrar\"\r\n >\r\n <lucide-icon name=\"x\"></lucide-icon>\r\n </button>\r\n <div class=\"SessionRequestContent\">\r\n <div class=\"SessionRequestIcon\">\r\n <lucide-icon [name]=\"sessionRequest.lucideIcon\"></lucide-icon>\r\n </div>\r\n <div class=\"SessionRequestMessage\">\r\n {{ sessionRequest.message }}\r\n </div>\r\n <div class=\"SessionRequestActions\">\r\n <a\r\n class=\"SessionRequestButton RegisterButton\"\r\n [routerLink]=\"config.sessionRegisterPath\"\r\n (click)=\"closeSessionRequest(sessionRequest.id)\"\r\n >\r\n <lucide-icon name=\"user-plus\"></lucide-icon>\r\n {{ config.sessionRegisterLabel }}\r\n </a>\r\n <a\r\n class=\"SessionRequestButton LoginButton\"\r\n [routerLink]=\"config.sessionLoginPath\"\r\n (click)=\"closeSessionRequest(sessionRequest.id)\"\r\n >\r\n <lucide-icon name=\"log-in\"></lucide-icon>\r\n {{ config.sessionLoginLabel }}\r\n </a>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".SessionRequestOverlay{position:fixed;top:0;left:0;width:100%;height:100%;background-color:color-mix(in srgb,var(--text-base) 50%,transparent);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;z-index:10000;pointer-events:auto;padding:4px}.SessionRequest{position:relative;background-color:var(--surface);border-radius:var(--radius-xl, var(--radius-lg));max-width:450px;width:100%;margin:12px;pointer-events:auto}.CloseButton{position:absolute;top:4px;right:4px;width:32px;height:32px;display:flex;align-items:center;justify-content:center;background-color:transparent;border:none;border-radius:50%;cursor:pointer;color:var(--text-muted);z-index:10}.CloseButton lucide-icon .lucide-svg{width:1.3rem;height:1.3rem}.SessionRequestContent{padding:12px;display:flex;flex-direction:column;gap:8px;align-items:center}.SessionRequestIcon{display:flex;justify-content:center;margin-top:12px}.SessionRequestIcon lucide-icon{--lucide-stroke: var(--primary)}.SessionRequestIcon lucide-icon .lucide-svg{width:2.5rem;height:2.5rem}.SessionRequestMessage{text-align:center;color:var(--text-base)}.SessionRequestActions{display:flex;gap:4px;width:100%;margin-top:8px}.SessionRequestButton{flex:1;padding:8px 16px;border:none;border-radius:var(--radius-lg);font-size:.9375rem;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:8px;text-decoration:none}.SessionRequestButton lucide-icon .lucide-svg{width:1rem;height:1rem}.LoginButton{background-color:var(--primary);color:var(--text-inverted)}.RegisterButton{background-color:var(--surface-secondary, var(--surface-2));color:var(--text-base)}\n"], dependencies: [{ kind: "component", type: LucideIconComponent, selector: "lucide-icon", inputs: ["name", "size", "spin", "filled"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i3.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }], animations: [
1200
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.11", type: SessionRequestServiceComponent, isStandalone: true, selector: "session-request-service", ngImport: i0, template: "<div class=\"SessionRequestOverlay\" *ngIf=\"sessionRequests.length > 0\">\r\n <div\r\n class=\"SessionRequest\"\r\n *ngFor=\"let sessionRequest of sessionRequests; trackBy: trackById\"\r\n [@sessionRequestAnimation]\r\n >\r\n <button\r\n type=\"button\"\r\n class=\"CloseButton\"\r\n (click)=\"closeSessionRequest(sessionRequest.id)\"\r\n title=\"Cerrar\"\r\n >\r\n <lucide-icon name=\"x\"></lucide-icon>\r\n </button>\r\n <div class=\"SessionRequestContent\">\r\n <div class=\"SessionRequestIcon\">\r\n <lucide-icon [name]=\"sessionRequest.lucideIcon\"></lucide-icon>\r\n </div>\r\n <div class=\"SessionRequestMessage\">\r\n {{ sessionRequest.message }}\r\n </div>\r\n <div class=\"SessionRequestActions\">\r\n <a\r\n class=\"SessionRequestButton RegisterButton\"\r\n [routerLink]=\"config.sessionRegisterPath\"\r\n (click)=\"closeSessionRequest(sessionRequest.id)\"\r\n >\r\n <lucide-icon name=\"user-plus\"></lucide-icon>\r\n {{ config.sessionRegisterLabel }}\r\n </a>\r\n <a\r\n class=\"SessionRequestButton LoginButton\"\r\n [routerLink]=\"config.sessionLoginPath\"\r\n (click)=\"closeSessionRequest(sessionRequest.id)\"\r\n >\r\n <lucide-icon name=\"log-in\"></lucide-icon>\r\n {{ config.sessionLoginLabel }}\r\n </a>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".SessionRequestOverlay{position:fixed;inset:0;background-color:var(--overlay-scrim, color-mix(in srgb, var(--text-base) 40%, transparent));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);display:flex;align-items:center;justify-content:center;z-index:10000;pointer-events:auto;padding:8px}.SessionRequest{position:relative;background-color:color-mix(in srgb,var(--surface) 94%,transparent);border-radius:var(--radius-xl, 20px);border:1px solid var(--border);max-width:420px;width:100%;margin:12px;pointer-events:auto;box-shadow:0 16px 48px color-mix(in srgb,var(--text-base) 16%,transparent);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);overflow:hidden}.CloseButton{position:absolute;top:4px;right:4px;width:32px;height:32px;display:flex;align-items:center;justify-content:center;background-color:transparent;border:none;border-radius:50%;cursor:pointer;color:var(--text-muted);z-index:10;transition:transform .1s ease,background-color .15s ease}.CloseButton:hover{background:var(--surface-2, var(--border))}.CloseButton:active{transform:scale(.98)}.CloseButton lucide-icon .lucide-svg{width:1.3rem;height:1.3rem}.SessionRequestContent{padding:16px;display:flex;flex-direction:column;gap:8px;align-items:center}.SessionRequestIcon{display:flex;justify-content:center;margin-top:8px}.SessionRequestIcon lucide-icon{--lucide-stroke: var(--primary)}.SessionRequestIcon lucide-icon .lucide-svg{width:2.5rem;height:2.5rem}.SessionRequestMessage{text-align:center;color:var(--text-base)}.SessionRequestActions{display:flex;gap:4px;width:100%;margin-top:8px}.SessionRequestButton{flex:1;height:32px;padding:4px 16px;border:none;border-radius:9999px;font-size:.875rem;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:8px;text-decoration:none;transition:transform .1s ease}.SessionRequestButton:active{transform:scale(.98)}.SessionRequestButton lucide-icon .lucide-svg{width:1rem;height:1rem}.LoginButton{background-color:var(--primary);color:var(--text-inverted)}.RegisterButton{background-color:var(--surface-2, var(--surface-secondary));color:var(--text-base)}\n"], dependencies: [{ kind: "component", type: LucideIconComponent, selector: "lucide-icon", inputs: ["name", "size", "spin", "filled"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i3.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }], animations: [
1188
1201
  trigger('sessionRequestAnimation', [
1189
1202
  transition(':enter', [
1190
1203
  style({ opacity: 0, transform: 'scale(0.9)' }),
@@ -1208,7 +1221,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImpo
1208
1221
  animate('200ms ease-in', style({ opacity: 0, transform: 'scale(0.9)' })),
1209
1222
  ]),
1210
1223
  ]),
1211
- ], template: "<div class=\"SessionRequestOverlay\" *ngIf=\"sessionRequests.length > 0\">\r\n <div\r\n class=\"SessionRequest\"\r\n *ngFor=\"let sessionRequest of sessionRequests; trackBy: trackById\"\r\n [@sessionRequestAnimation]\r\n >\r\n <button\r\n type=\"button\"\r\n class=\"CloseButton\"\r\n (click)=\"closeSessionRequest(sessionRequest.id)\"\r\n title=\"Cerrar\"\r\n >\r\n <lucide-icon name=\"x\"></lucide-icon>\r\n </button>\r\n <div class=\"SessionRequestContent\">\r\n <div class=\"SessionRequestIcon\">\r\n <lucide-icon [name]=\"sessionRequest.lucideIcon\"></lucide-icon>\r\n </div>\r\n <div class=\"SessionRequestMessage\">\r\n {{ sessionRequest.message }}\r\n </div>\r\n <div class=\"SessionRequestActions\">\r\n <a\r\n class=\"SessionRequestButton RegisterButton\"\r\n [routerLink]=\"config.sessionRegisterPath\"\r\n (click)=\"closeSessionRequest(sessionRequest.id)\"\r\n >\r\n <lucide-icon name=\"user-plus\"></lucide-icon>\r\n {{ config.sessionRegisterLabel }}\r\n </a>\r\n <a\r\n class=\"SessionRequestButton LoginButton\"\r\n [routerLink]=\"config.sessionLoginPath\"\r\n (click)=\"closeSessionRequest(sessionRequest.id)\"\r\n >\r\n <lucide-icon name=\"log-in\"></lucide-icon>\r\n {{ config.sessionLoginLabel }}\r\n </a>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".SessionRequestOverlay{position:fixed;top:0;left:0;width:100%;height:100%;background-color:color-mix(in srgb,var(--text-base) 50%,transparent);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;z-index:10000;pointer-events:auto;padding:4px}.SessionRequest{position:relative;background-color:var(--surface);border-radius:var(--radius-xl, var(--radius-lg));max-width:450px;width:100%;margin:12px;pointer-events:auto}.CloseButton{position:absolute;top:4px;right:4px;width:32px;height:32px;display:flex;align-items:center;justify-content:center;background-color:transparent;border:none;border-radius:50%;cursor:pointer;color:var(--text-muted);z-index:10}.CloseButton lucide-icon .lucide-svg{width:1.3rem;height:1.3rem}.SessionRequestContent{padding:12px;display:flex;flex-direction:column;gap:8px;align-items:center}.SessionRequestIcon{display:flex;justify-content:center;margin-top:12px}.SessionRequestIcon lucide-icon{--lucide-stroke: var(--primary)}.SessionRequestIcon lucide-icon .lucide-svg{width:2.5rem;height:2.5rem}.SessionRequestMessage{text-align:center;color:var(--text-base)}.SessionRequestActions{display:flex;gap:4px;width:100%;margin-top:8px}.SessionRequestButton{flex:1;padding:8px 16px;border:none;border-radius:var(--radius-lg);font-size:.9375rem;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:8px;text-decoration:none}.SessionRequestButton lucide-icon .lucide-svg{width:1rem;height:1rem}.LoginButton{background-color:var(--primary);color:var(--text-inverted)}.RegisterButton{background-color:var(--surface-secondary, var(--surface-2));color:var(--text-base)}\n"] }]
1224
+ ], template: "<div class=\"SessionRequestOverlay\" *ngIf=\"sessionRequests.length > 0\">\r\n <div\r\n class=\"SessionRequest\"\r\n *ngFor=\"let sessionRequest of sessionRequests; trackBy: trackById\"\r\n [@sessionRequestAnimation]\r\n >\r\n <button\r\n type=\"button\"\r\n class=\"CloseButton\"\r\n (click)=\"closeSessionRequest(sessionRequest.id)\"\r\n title=\"Cerrar\"\r\n >\r\n <lucide-icon name=\"x\"></lucide-icon>\r\n </button>\r\n <div class=\"SessionRequestContent\">\r\n <div class=\"SessionRequestIcon\">\r\n <lucide-icon [name]=\"sessionRequest.lucideIcon\"></lucide-icon>\r\n </div>\r\n <div class=\"SessionRequestMessage\">\r\n {{ sessionRequest.message }}\r\n </div>\r\n <div class=\"SessionRequestActions\">\r\n <a\r\n class=\"SessionRequestButton RegisterButton\"\r\n [routerLink]=\"config.sessionRegisterPath\"\r\n (click)=\"closeSessionRequest(sessionRequest.id)\"\r\n >\r\n <lucide-icon name=\"user-plus\"></lucide-icon>\r\n {{ config.sessionRegisterLabel }}\r\n </a>\r\n <a\r\n class=\"SessionRequestButton LoginButton\"\r\n [routerLink]=\"config.sessionLoginPath\"\r\n (click)=\"closeSessionRequest(sessionRequest.id)\"\r\n >\r\n <lucide-icon name=\"log-in\"></lucide-icon>\r\n {{ config.sessionLoginLabel }}\r\n </a>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".SessionRequestOverlay{position:fixed;inset:0;background-color:var(--overlay-scrim, color-mix(in srgb, var(--text-base) 40%, transparent));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);display:flex;align-items:center;justify-content:center;z-index:10000;pointer-events:auto;padding:8px}.SessionRequest{position:relative;background-color:color-mix(in srgb,var(--surface) 94%,transparent);border-radius:var(--radius-xl, 20px);border:1px solid var(--border);max-width:420px;width:100%;margin:12px;pointer-events:auto;box-shadow:0 16px 48px color-mix(in srgb,var(--text-base) 16%,transparent);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);overflow:hidden}.CloseButton{position:absolute;top:4px;right:4px;width:32px;height:32px;display:flex;align-items:center;justify-content:center;background-color:transparent;border:none;border-radius:50%;cursor:pointer;color:var(--text-muted);z-index:10;transition:transform .1s ease,background-color .15s ease}.CloseButton:hover{background:var(--surface-2, var(--border))}.CloseButton:active{transform:scale(.98)}.CloseButton lucide-icon .lucide-svg{width:1.3rem;height:1.3rem}.SessionRequestContent{padding:16px;display:flex;flex-direction:column;gap:8px;align-items:center}.SessionRequestIcon{display:flex;justify-content:center;margin-top:8px}.SessionRequestIcon lucide-icon{--lucide-stroke: var(--primary)}.SessionRequestIcon lucide-icon .lucide-svg{width:2.5rem;height:2.5rem}.SessionRequestMessage{text-align:center;color:var(--text-base)}.SessionRequestActions{display:flex;gap:4px;width:100%;margin-top:8px}.SessionRequestButton{flex:1;height:32px;padding:4px 16px;border:none;border-radius:9999px;font-size:.875rem;font-weight:600;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:8px;text-decoration:none;transition:transform .1s ease}.SessionRequestButton:active{transform:scale(.98)}.SessionRequestButton lucide-icon .lucide-svg{width:1rem;height:1rem}.LoginButton{background-color:var(--primary);color:var(--text-inverted)}.RegisterButton{background-color:var(--surface-2, var(--surface-secondary));color:var(--text-base)}\n"] }]
1212
1225
  }], ctorParameters: () => [{ type: SessionRequestService }] });
1213
1226
 
1214
1227
  /**
@@ -1279,11 +1292,11 @@ class GlobalLoadingServiceComponent {
1279
1292
  return item.id;
1280
1293
  }
1281
1294
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: GlobalLoadingServiceComponent, deps: [{ token: GlobalLoadingService }], target: i0.ɵɵFactoryTarget.Component });
1282
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.11", type: GlobalLoadingServiceComponent, isStandalone: true, selector: "global-loading-service", ngImport: i0, template: "<div\r\n *ngIf=\"processes.length > 0\"\r\n class=\"GlobalLoadingOverlay\"\r\n aria-live=\"polite\"\r\n aria-busy=\"true\"\r\n role=\"alertdialog\"\r\n aria-modal=\"true\"\r\n>\r\n <div class=\"GlobalLoadingCard\">\r\n <div class=\"GlobalLoadingSpinner\" aria-hidden=\"true\"></div>\r\n <ul class=\"GlobalLoadingList\">\r\n <li *ngFor=\"let p of processes; trackBy: trackById\" class=\"GlobalLoadingItem\">\r\n {{ p.message }}\r\n </li>\r\n </ul>\r\n </div>\r\n</div>\r\n", styles: [".GlobalLoadingOverlay{position:fixed;inset:0;z-index:10050;display:flex;align-items:center;justify-content:center;padding:16px;background-color:color-mix(in srgb,var(--text-base) 45%,transparent);backdrop-filter:blur(5px);pointer-events:auto}.GlobalLoadingCard{display:flex;flex-direction:column;align-items:center;gap:8px;max-width:420px;width:100%;padding:16px;background:var(--surface);border-radius:var(--radius-lg);border:1px solid var(--border);box-shadow:0 12px 40px color-mix(in srgb,var(--text-base) 15%,transparent)}.GlobalLoadingSpinner{width:48px;height:48px;border:3px solid var(--border);border-top-color:var(--primary);border-radius:50%;animation:GlobalLoadingSpin .75s linear infinite}.GlobalLoadingList{list-style:none;margin:0;padding:0;width:100%;text-align:center}.GlobalLoadingItem{font-size:.95rem;font-weight:500;color:var(--text-primary, var(--text-base));line-height:1.45}.GlobalLoadingItem+.GlobalLoadingItem{margin-top:8px;padding-top:8px;border-top:1px solid var(--border)}@keyframes GlobalLoadingSpin{to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1295
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.11", type: GlobalLoadingServiceComponent, isStandalone: true, selector: "global-loading-service", ngImport: i0, template: "<div\r\n *ngIf=\"processes.length > 0\"\r\n class=\"GlobalLoadingOverlay\"\r\n aria-live=\"polite\"\r\n aria-busy=\"true\"\r\n role=\"alertdialog\"\r\n aria-modal=\"true\"\r\n>\r\n <div class=\"GlobalLoadingCard\">\r\n <div class=\"GlobalLoadingSpinner\" aria-hidden=\"true\"></div>\r\n <ul class=\"GlobalLoadingList\">\r\n <li *ngFor=\"let p of processes; trackBy: trackById\" class=\"GlobalLoadingItem\">\r\n {{ p.message }}\r\n </li>\r\n </ul>\r\n </div>\r\n</div>\r\n", styles: [".GlobalLoadingOverlay{position:fixed;inset:0;z-index:10050;display:flex;align-items:center;justify-content:center;padding:16px;background-color:var(--overlay-scrim, color-mix(in srgb, var(--text-base) 40%, transparent));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);pointer-events:auto}.GlobalLoadingCard{display:flex;flex-direction:column;align-items:center;gap:8px;max-width:420px;width:100%;padding:16px;background:color-mix(in srgb,var(--surface) 92%,transparent);border-radius:var(--radius-xl, 20px);border:1px solid var(--border);box-shadow:0 16px 48px color-mix(in srgb,var(--text-base) 16%,transparent);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px)}.GlobalLoadingSpinner{width:40px;height:40px;border:2px solid color-mix(in srgb,var(--primary) 25%,transparent);border-top-color:var(--primary);border-radius:50%;animation:GlobalLoadingSpin .75s linear infinite}.GlobalLoadingList{list-style:none;margin:0;padding:0;width:100%;text-align:center}.GlobalLoadingItem{font-size:.9375rem;font-weight:500;color:var(--text-primary, var(--text-base));line-height:1.45}.GlobalLoadingItem+.GlobalLoadingItem{margin-top:8px;padding-top:8px;border-top:1px solid var(--border)}@keyframes GlobalLoadingSpin{to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1283
1296
  }
1284
1297
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: GlobalLoadingServiceComponent, decorators: [{
1285
1298
  type: Component,
1286
- args: [{ selector: 'global-loading-service', standalone: true, imports: [CommonModule], template: "<div\r\n *ngIf=\"processes.length > 0\"\r\n class=\"GlobalLoadingOverlay\"\r\n aria-live=\"polite\"\r\n aria-busy=\"true\"\r\n role=\"alertdialog\"\r\n aria-modal=\"true\"\r\n>\r\n <div class=\"GlobalLoadingCard\">\r\n <div class=\"GlobalLoadingSpinner\" aria-hidden=\"true\"></div>\r\n <ul class=\"GlobalLoadingList\">\r\n <li *ngFor=\"let p of processes; trackBy: trackById\" class=\"GlobalLoadingItem\">\r\n {{ p.message }}\r\n </li>\r\n </ul>\r\n </div>\r\n</div>\r\n", styles: [".GlobalLoadingOverlay{position:fixed;inset:0;z-index:10050;display:flex;align-items:center;justify-content:center;padding:16px;background-color:color-mix(in srgb,var(--text-base) 45%,transparent);backdrop-filter:blur(5px);pointer-events:auto}.GlobalLoadingCard{display:flex;flex-direction:column;align-items:center;gap:8px;max-width:420px;width:100%;padding:16px;background:var(--surface);border-radius:var(--radius-lg);border:1px solid var(--border);box-shadow:0 12px 40px color-mix(in srgb,var(--text-base) 15%,transparent)}.GlobalLoadingSpinner{width:48px;height:48px;border:3px solid var(--border);border-top-color:var(--primary);border-radius:50%;animation:GlobalLoadingSpin .75s linear infinite}.GlobalLoadingList{list-style:none;margin:0;padding:0;width:100%;text-align:center}.GlobalLoadingItem{font-size:.95rem;font-weight:500;color:var(--text-primary, var(--text-base));line-height:1.45}.GlobalLoadingItem+.GlobalLoadingItem{margin-top:8px;padding-top:8px;border-top:1px solid var(--border)}@keyframes GlobalLoadingSpin{to{transform:rotate(360deg)}}\n"] }]
1299
+ args: [{ selector: 'global-loading-service', standalone: true, imports: [CommonModule], template: "<div\r\n *ngIf=\"processes.length > 0\"\r\n class=\"GlobalLoadingOverlay\"\r\n aria-live=\"polite\"\r\n aria-busy=\"true\"\r\n role=\"alertdialog\"\r\n aria-modal=\"true\"\r\n>\r\n <div class=\"GlobalLoadingCard\">\r\n <div class=\"GlobalLoadingSpinner\" aria-hidden=\"true\"></div>\r\n <ul class=\"GlobalLoadingList\">\r\n <li *ngFor=\"let p of processes; trackBy: trackById\" class=\"GlobalLoadingItem\">\r\n {{ p.message }}\r\n </li>\r\n </ul>\r\n </div>\r\n</div>\r\n", styles: [".GlobalLoadingOverlay{position:fixed;inset:0;z-index:10050;display:flex;align-items:center;justify-content:center;padding:16px;background-color:var(--overlay-scrim, color-mix(in srgb, var(--text-base) 40%, transparent));backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);pointer-events:auto}.GlobalLoadingCard{display:flex;flex-direction:column;align-items:center;gap:8px;max-width:420px;width:100%;padding:16px;background:color-mix(in srgb,var(--surface) 92%,transparent);border-radius:var(--radius-xl, 20px);border:1px solid var(--border);box-shadow:0 16px 48px color-mix(in srgb,var(--text-base) 16%,transparent);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px)}.GlobalLoadingSpinner{width:40px;height:40px;border:2px solid color-mix(in srgb,var(--primary) 25%,transparent);border-top-color:var(--primary);border-radius:50%;animation:GlobalLoadingSpin .75s linear infinite}.GlobalLoadingList{list-style:none;margin:0;padding:0;width:100%;text-align:center}.GlobalLoadingItem{font-size:.9375rem;font-weight:500;color:var(--text-primary, var(--text-base));line-height:1.45}.GlobalLoadingItem+.GlobalLoadingItem{margin-top:8px;padding-top:8px;border-top:1px solid var(--border)}@keyframes GlobalLoadingSpin{to{transform:rotate(360deg)}}\n"] }]
1287
1300
  }], ctorParameters: () => [{ type: GlobalLoadingService }] });
1288
1301
 
1289
1302
  const SHOW_DELAY_MS = 120;
@@ -1645,6 +1658,377 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImpo
1645
1658
  args: [PLATFORM_ID]
1646
1659
  }] }] });
1647
1660
 
1661
+ /**
1662
+ * Bloqueo de scroll del body para modales/overlays.
1663
+ * Contador por clave: varios overlays pueden compartir el lock.
1664
+ */
1665
+ class ScrollLockService {
1666
+ locks = new Set();
1667
+ isBrowser;
1668
+ prevOverflow = '';
1669
+ constructor(platformId) {
1670
+ this.isBrowser = isPlatformBrowser(platformId);
1671
+ }
1672
+ lock(key = 'default') {
1673
+ if (!this.isBrowser)
1674
+ return;
1675
+ const wasEmpty = this.locks.size === 0;
1676
+ this.locks.add(key);
1677
+ if (wasEmpty) {
1678
+ this.prevOverflow = document.body.style.overflow;
1679
+ document.body.style.overflow = 'hidden';
1680
+ }
1681
+ }
1682
+ unlock(key = 'default') {
1683
+ if (!this.isBrowser)
1684
+ return;
1685
+ this.locks.delete(key);
1686
+ if (this.locks.size === 0) {
1687
+ document.body.style.overflow = this.prevOverflow;
1688
+ this.prevOverflow = '';
1689
+ }
1690
+ }
1691
+ clear() {
1692
+ if (!this.isBrowser)
1693
+ return;
1694
+ this.locks.clear();
1695
+ document.body.style.overflow = this.prevOverflow;
1696
+ this.prevOverflow = '';
1697
+ }
1698
+ get isLocked() {
1699
+ return this.locks.size > 0;
1700
+ }
1701
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: ScrollLockService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable });
1702
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: ScrollLockService, providedIn: 'root' });
1703
+ }
1704
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: ScrollLockService, decorators: [{
1705
+ type: Injectable,
1706
+ args: [{ providedIn: 'root' }]
1707
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
1708
+ type: Inject,
1709
+ args: [PLATFORM_ID]
1710
+ }] }] });
1711
+
1712
+ class ActionSheetService {
1713
+ platformId;
1714
+ requestSubject = new Subject();
1715
+ request$ = this.requestSubject.asObservable();
1716
+ closeSubject = new Subject();
1717
+ close$ = this.closeSubject.asObservable();
1718
+ idCounter = 0;
1719
+ isBrowser;
1720
+ scrollLock = inject(ScrollLockService);
1721
+ constructor(platformId) {
1722
+ this.platformId = platformId;
1723
+ this.isBrowser = isPlatformBrowser(this.platformId);
1724
+ }
1725
+ open(options, opts) {
1726
+ if (!this.isBrowser || options.length === 0) {
1727
+ return Promise.resolve(null);
1728
+ }
1729
+ this.scrollLock.lock('action-sheet');
1730
+ return new Promise((resolve) => {
1731
+ const request = {
1732
+ id: ++this.idCounter,
1733
+ title: opts?.title,
1734
+ message: opts?.message,
1735
+ options,
1736
+ resolve: (value) => {
1737
+ this.scrollLock.unlock('action-sheet');
1738
+ resolve(value);
1739
+ },
1740
+ };
1741
+ this.requestSubject.next(request);
1742
+ });
1743
+ }
1744
+ close() {
1745
+ if (!this.isBrowser)
1746
+ return;
1747
+ this.closeSubject.next();
1748
+ }
1749
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: ActionSheetService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable });
1750
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: ActionSheetService, providedIn: 'root' });
1751
+ }
1752
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: ActionSheetService, decorators: [{
1753
+ type: Injectable,
1754
+ args: [{ providedIn: 'root' }]
1755
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
1756
+ type: Inject,
1757
+ args: [PLATFORM_ID]
1758
+ }] }] });
1759
+
1760
+ class ActionSheetServiceComponent {
1761
+ service = inject(ActionSheetService);
1762
+ sub = Subscription.EMPTY;
1763
+ closeSub = Subscription.EMPTY;
1764
+ request = null;
1765
+ ngOnInit() {
1766
+ this.sub = this.service.request$.subscribe((req) => {
1767
+ this.request = req;
1768
+ });
1769
+ this.closeSub = this.service.close$.subscribe(() => this.dismiss());
1770
+ }
1771
+ ngOnDestroy() {
1772
+ this.sub.unsubscribe();
1773
+ this.closeSub.unsubscribe();
1774
+ }
1775
+ select(opt) {
1776
+ if (!this.request || opt.disabled)
1777
+ return;
1778
+ const resolve = this.request.resolve;
1779
+ this.request = null;
1780
+ resolve(opt.cancel ? null : opt);
1781
+ }
1782
+ dismiss() {
1783
+ if (!this.request)
1784
+ return;
1785
+ const resolve = this.request.resolve;
1786
+ this.request = null;
1787
+ resolve(null);
1788
+ }
1789
+ mainOptions(options) {
1790
+ return options.filter((o) => !o.cancel);
1791
+ }
1792
+ cancelOption(options) {
1793
+ return options.find((o) => o.cancel) ?? null;
1794
+ }
1795
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: ActionSheetServiceComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1796
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.11", type: ActionSheetServiceComponent, isStandalone: true, selector: "action-sheet-service", ngImport: i0, template: "@if (request) {\r\n <div class=\"ActionSheetOverlay\" (click)=\"dismiss()\">\r\n <div class=\"ActionSheet\" [@sheetAnim] (click)=\"$event.stopPropagation()\">\r\n @if (request.title || request.message) {\r\n <div class=\"ActionSheet__Header\">\r\n @if (request.title) {\r\n <p class=\"ActionSheet__Title\">{{ request.title }}</p>\r\n }\r\n @if (request.message) {\r\n <p class=\"ActionSheet__Message\">{{ request.message }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"ActionSheet__Group\">\r\n @for (opt of mainOptions(request.options); track $index) {\r\n <button\r\n type=\"button\"\r\n class=\"ActionSheet__Item\"\r\n [class.ActionSheet__Item--danger]=\"opt.danger\"\r\n [disabled]=\"opt.disabled\"\r\n (click)=\"select(opt)\"\r\n >\r\n @if (opt.icon) {\r\n <lucide-icon [name]=\"opt.icon\" [size]=\"18\"></lucide-icon>\r\n }\r\n <span>{{ opt.label }}</span>\r\n </button>\r\n }\r\n </div>\r\n\r\n @if (cancelOption(request.options); as cancel) {\r\n <div class=\"ActionSheet__Group ActionSheet__Group--cancel\">\r\n <button type=\"button\" class=\"ActionSheet__Item ActionSheet__Item--cancel\" (click)=\"select(cancel)\">\r\n {{ cancel.label }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n}\r\n", styles: [".ActionSheetOverlay{position:fixed;inset:0;z-index:10020;display:flex;align-items:flex-end;justify-content:center;padding:8px;padding-bottom:max(8px,env(safe-area-inset-bottom,8px));background:var(--overlay-scrim, color-mix(in srgb, var(--text-base) 40%, transparent));backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.ActionSheet{width:100%;max-width:420px;display:flex;flex-direction:column;gap:8px}.ActionSheet__Header{padding:12px 16px;text-align:center;background:color-mix(in srgb,var(--surface) 92%,transparent);border-radius:var(--radius-xl, 14px);border:1px solid var(--border);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px)}.ActionSheet__Title{margin:0;font-size:.8125rem;font-weight:600;color:var(--text-muted)}.ActionSheet__Message{margin:4px 0 0;font-size:.8125rem;line-height:1.4;color:var(--text-muted)}.ActionSheet__Group{display:flex;flex-direction:column;overflow:hidden;background:color-mix(in srgb,var(--surface) 94%,transparent);border-radius:var(--radius-xl, 14px);border:1px solid var(--border);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px)}.ActionSheet__Group--cancel{margin-top:0}.ActionSheet__Item{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;min-height:44px;padding:8px 16px;border:none;border-bottom:1px solid var(--border);background:transparent;color:var(--primary);font:inherit;font-size:1rem;font-weight:500;cursor:pointer;transition:background-color .12s ease,transform .1s ease}.ActionSheet__Item:last-child{border-bottom:none}.ActionSheet__Item:hover:not(:disabled){background:color-mix(in srgb,var(--text-base) 4%,transparent)}.ActionSheet__Item:active:not(:disabled){transform:scale(.98)}.ActionSheet__Item:disabled{opacity:.45;cursor:not-allowed}.ActionSheet__Item lucide-icon{--lucide-stroke: currentColor}.ActionSheet__Item--danger{color:var(--error-text)}.ActionSheet__Item--cancel{font-weight:600;color:var(--text-base)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: LucideIconComponent, selector: "lucide-icon", inputs: ["name", "size", "spin", "filled"] }], animations: [
1797
+ trigger('sheetAnim', [
1798
+ transition(':enter', [
1799
+ style({ opacity: 0, transform: 'translateY(24px)' }),
1800
+ animate('220ms ease-out', style({ opacity: 1, transform: 'translateY(0)' })),
1801
+ ]),
1802
+ transition(':leave', [
1803
+ animate('160ms ease-in', style({ opacity: 0, transform: 'translateY(16px)' })),
1804
+ ]),
1805
+ ]),
1806
+ ] });
1807
+ }
1808
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: ActionSheetServiceComponent, decorators: [{
1809
+ type: Component,
1810
+ args: [{ selector: 'action-sheet-service', standalone: true, imports: [CommonModule, LucideIconComponent], animations: [
1811
+ trigger('sheetAnim', [
1812
+ transition(':enter', [
1813
+ style({ opacity: 0, transform: 'translateY(24px)' }),
1814
+ animate('220ms ease-out', style({ opacity: 1, transform: 'translateY(0)' })),
1815
+ ]),
1816
+ transition(':leave', [
1817
+ animate('160ms ease-in', style({ opacity: 0, transform: 'translateY(16px)' })),
1818
+ ]),
1819
+ ]),
1820
+ ], template: "@if (request) {\r\n <div class=\"ActionSheetOverlay\" (click)=\"dismiss()\">\r\n <div class=\"ActionSheet\" [@sheetAnim] (click)=\"$event.stopPropagation()\">\r\n @if (request.title || request.message) {\r\n <div class=\"ActionSheet__Header\">\r\n @if (request.title) {\r\n <p class=\"ActionSheet__Title\">{{ request.title }}</p>\r\n }\r\n @if (request.message) {\r\n <p class=\"ActionSheet__Message\">{{ request.message }}</p>\r\n }\r\n </div>\r\n }\r\n\r\n <div class=\"ActionSheet__Group\">\r\n @for (opt of mainOptions(request.options); track $index) {\r\n <button\r\n type=\"button\"\r\n class=\"ActionSheet__Item\"\r\n [class.ActionSheet__Item--danger]=\"opt.danger\"\r\n [disabled]=\"opt.disabled\"\r\n (click)=\"select(opt)\"\r\n >\r\n @if (opt.icon) {\r\n <lucide-icon [name]=\"opt.icon\" [size]=\"18\"></lucide-icon>\r\n }\r\n <span>{{ opt.label }}</span>\r\n </button>\r\n }\r\n </div>\r\n\r\n @if (cancelOption(request.options); as cancel) {\r\n <div class=\"ActionSheet__Group ActionSheet__Group--cancel\">\r\n <button type=\"button\" class=\"ActionSheet__Item ActionSheet__Item--cancel\" (click)=\"select(cancel)\">\r\n {{ cancel.label }}\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n}\r\n", styles: [".ActionSheetOverlay{position:fixed;inset:0;z-index:10020;display:flex;align-items:flex-end;justify-content:center;padding:8px;padding-bottom:max(8px,env(safe-area-inset-bottom,8px));background:var(--overlay-scrim, color-mix(in srgb, var(--text-base) 40%, transparent));backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}.ActionSheet{width:100%;max-width:420px;display:flex;flex-direction:column;gap:8px}.ActionSheet__Header{padding:12px 16px;text-align:center;background:color-mix(in srgb,var(--surface) 92%,transparent);border-radius:var(--radius-xl, 14px);border:1px solid var(--border);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px)}.ActionSheet__Title{margin:0;font-size:.8125rem;font-weight:600;color:var(--text-muted)}.ActionSheet__Message{margin:4px 0 0;font-size:.8125rem;line-height:1.4;color:var(--text-muted)}.ActionSheet__Group{display:flex;flex-direction:column;overflow:hidden;background:color-mix(in srgb,var(--surface) 94%,transparent);border-radius:var(--radius-xl, 14px);border:1px solid var(--border);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px)}.ActionSheet__Group--cancel{margin-top:0}.ActionSheet__Item{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;min-height:44px;padding:8px 16px;border:none;border-bottom:1px solid var(--border);background:transparent;color:var(--primary);font:inherit;font-size:1rem;font-weight:500;cursor:pointer;transition:background-color .12s ease,transform .1s ease}.ActionSheet__Item:last-child{border-bottom:none}.ActionSheet__Item:hover:not(:disabled){background:color-mix(in srgb,var(--text-base) 4%,transparent)}.ActionSheet__Item:active:not(:disabled){transform:scale(.98)}.ActionSheet__Item:disabled{opacity:.45;cursor:not-allowed}.ActionSheet__Item lucide-icon{--lucide-stroke: currentColor}.ActionSheet__Item--danger{color:var(--error-text)}.ActionSheet__Item--cancel{font-weight:600;color:var(--text-base)}\n"] }]
1821
+ }] });
1822
+
1823
+ class CopyFeedbackService {
1824
+ isBrowser;
1825
+ toast = inject(ToastService);
1826
+ config = inject(XOSUE_UTILS_SERVICES_CONFIG);
1827
+ constructor(platformId) {
1828
+ this.isBrowser = isPlatformBrowser(platformId);
1829
+ }
1830
+ /** Copia texto al portapapeles y muestra toast de feedback. */
1831
+ async copyText(text, successMessage) {
1832
+ if (!this.isBrowser)
1833
+ return false;
1834
+ try {
1835
+ if (navigator.clipboard?.writeText) {
1836
+ await navigator.clipboard.writeText(text);
1837
+ }
1838
+ else {
1839
+ const ta = document.createElement('textarea');
1840
+ ta.value = text;
1841
+ ta.setAttribute('readonly', '');
1842
+ ta.style.position = 'fixed';
1843
+ ta.style.opacity = '0';
1844
+ document.body.appendChild(ta);
1845
+ ta.select();
1846
+ document.execCommand('copy');
1847
+ document.body.removeChild(ta);
1848
+ }
1849
+ this.toast.showToast('success', successMessage ?? this.config.copySuccessLabel);
1850
+ return true;
1851
+ }
1852
+ catch {
1853
+ this.toast.showToast('error', this.config.copyErrorLabel);
1854
+ return false;
1855
+ }
1856
+ }
1857
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: CopyFeedbackService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable });
1858
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: CopyFeedbackService, providedIn: 'root' });
1859
+ }
1860
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: CopyFeedbackService, decorators: [{
1861
+ type: Injectable,
1862
+ args: [{ providedIn: 'root' }]
1863
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
1864
+ type: Inject,
1865
+ args: [PLATFORM_ID]
1866
+ }] }] });
1867
+
1868
+ class NetworkStatusService {
1869
+ isBrowser;
1870
+ onlineSubject;
1871
+ sub = Subscription.EMPTY;
1872
+ online$;
1873
+ constructor(platformId) {
1874
+ this.isBrowser = isPlatformBrowser(platformId);
1875
+ const initial = this.isBrowser ? navigator.onLine : true;
1876
+ this.onlineSubject = new BehaviorSubject(initial);
1877
+ this.online$ = this.onlineSubject.asObservable();
1878
+ if (this.isBrowser) {
1879
+ this.sub = merge(fromEvent(window, 'online').pipe(map(() => true)), fromEvent(window, 'offline').pipe(map(() => false)))
1880
+ .pipe(startWith(navigator.onLine))
1881
+ .subscribe((online) => this.onlineSubject.next(online));
1882
+ }
1883
+ }
1884
+ get isOnline() {
1885
+ return this.onlineSubject.value;
1886
+ }
1887
+ ngOnDestroy() {
1888
+ this.sub.unsubscribe();
1889
+ }
1890
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NetworkStatusService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable });
1891
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NetworkStatusService, providedIn: 'root' });
1892
+ }
1893
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NetworkStatusService, decorators: [{
1894
+ type: Injectable,
1895
+ args: [{ providedIn: 'root' }]
1896
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
1897
+ type: Inject,
1898
+ args: [PLATFORM_ID]
1899
+ }] }] });
1900
+
1901
+ class NetworkStatusServiceComponent {
1902
+ network = inject(NetworkStatusService);
1903
+ config = inject(XOSUE_UTILS_SERVICES_CONFIG);
1904
+ online$ = this.network.online$;
1905
+ sub = Subscription.EMPTY;
1906
+ ngOnInit() {
1907
+ // Ensure service is constructed early.
1908
+ this.sub = this.online$.subscribe();
1909
+ }
1910
+ ngOnDestroy() {
1911
+ this.sub.unsubscribe();
1912
+ }
1913
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NetworkStatusServiceComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1914
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.11", type: NetworkStatusServiceComponent, isStandalone: true, selector: "network-status-service", ngImport: i0, template: `
1915
+ @if ((online$ | async) === false) {
1916
+ <div class="NetworkBanner" role="status">
1917
+ <lucide-icon name="wifi-off" [size]="16"></lucide-icon>
1918
+ <span>{{ config.networkOfflineLabel }}</span>
1919
+ </div>
1920
+ }
1921
+ `, isInline: true, styles: [":host{display:contents}.NetworkBanner{position:fixed;top:0;left:0;right:0;z-index:10060;display:flex;align-items:center;justify-content:center;gap:8px;padding:8px 12px;padding-top:max(8px,env(safe-area-inset-top,8px));background:color-mix(in srgb,var(--warning-bg) 90%,var(--surface));color:var(--warning-text);border-bottom:1px solid var(--warning-border);font-size:.8125rem;font-weight:600;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}.NetworkBanner lucide-icon{--lucide-stroke: var(--warning-text)}\n"], dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "component", type: LucideIconComponent, selector: "lucide-icon", inputs: ["name", "size", "spin", "filled"] }] });
1922
+ }
1923
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NetworkStatusServiceComponent, decorators: [{
1924
+ type: Component,
1925
+ args: [{ selector: 'network-status-service', standalone: true, imports: [AsyncPipe, LucideIconComponent], template: `
1926
+ @if ((online$ | async) === false) {
1927
+ <div class="NetworkBanner" role="status">
1928
+ <lucide-icon name="wifi-off" [size]="16"></lucide-icon>
1929
+ <span>{{ config.networkOfflineLabel }}</span>
1930
+ </div>
1931
+ }
1932
+ `, styles: [":host{display:contents}.NetworkBanner{position:fixed;top:0;left:0;right:0;z-index:10060;display:flex;align-items:center;justify-content:center;gap:8px;padding:8px 12px;padding-top:max(8px,env(safe-area-inset-top,8px));background:color-mix(in srgb,var(--warning-bg) 90%,var(--surface));color:var(--warning-text);border-bottom:1px solid var(--warning-border);font-size:.8125rem;font-weight:600;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}.NetworkBanner lucide-icon{--lucide-stroke: var(--warning-text)}\n"] }]
1933
+ }] });
1934
+
1935
+ /**
1936
+ * Registro mínimo de atajos de teclado (global).
1937
+ */
1938
+ class HotkeyService {
1939
+ isBrowser;
1940
+ map = new Map();
1941
+ onKey;
1942
+ constructor(platformId) {
1943
+ this.isBrowser = isPlatformBrowser(platformId);
1944
+ this.onKey = (e) => this.handle(e);
1945
+ if (this.isBrowser) {
1946
+ window.addEventListener('keydown', this.onKey);
1947
+ }
1948
+ }
1949
+ register(id, registration) {
1950
+ this.map.set(id, registration);
1951
+ }
1952
+ unregister(id) {
1953
+ this.map.delete(id);
1954
+ }
1955
+ clear() {
1956
+ this.map.clear();
1957
+ }
1958
+ ngOnDestroy() {
1959
+ if (this.isBrowser) {
1960
+ window.removeEventListener('keydown', this.onKey);
1961
+ }
1962
+ this.map.clear();
1963
+ }
1964
+ handle(event) {
1965
+ if (!this.isBrowser)
1966
+ return;
1967
+ const target = event.target;
1968
+ if (target) {
1969
+ const tag = target.tagName;
1970
+ if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target.isContentEditable) {
1971
+ // Permitir Escape aún en inputs
1972
+ if (event.key !== 'Escape')
1973
+ return;
1974
+ }
1975
+ }
1976
+ for (const reg of this.map.values()) {
1977
+ if (this.matches(event, reg.combo)) {
1978
+ if (reg.preventDefault !== false) {
1979
+ event.preventDefault();
1980
+ }
1981
+ reg.handler(event);
1982
+ }
1983
+ }
1984
+ }
1985
+ matches(event, combo) {
1986
+ const parts = combo.toLowerCase().split('+').map((p) => p.trim()).filter(Boolean);
1987
+ let needCtrl = false;
1988
+ let needMeta = false;
1989
+ let needAlt = false;
1990
+ let needShift = false;
1991
+ let key = '';
1992
+ for (const part of parts) {
1993
+ if (part === 'ctrl' || part === 'control')
1994
+ needCtrl = true;
1995
+ else if (part === 'meta' || part === 'cmd' || part === 'command')
1996
+ needMeta = true;
1997
+ else if (part === 'alt' || part === 'option')
1998
+ needAlt = true;
1999
+ else if (part === 'shift')
2000
+ needShift = true;
2001
+ else
2002
+ key = part;
2003
+ }
2004
+ if (!!event.ctrlKey !== needCtrl)
2005
+ return false;
2006
+ if (!!event.metaKey !== needMeta)
2007
+ return false;
2008
+ if (!!event.altKey !== needAlt)
2009
+ return false;
2010
+ if (!!event.shiftKey !== needShift)
2011
+ return false;
2012
+ const eventKey = event.key.toLowerCase();
2013
+ if (key === 'escape' || key === 'esc')
2014
+ return eventKey === 'escape';
2015
+ if (key === 'enter' || key === 'return')
2016
+ return eventKey === 'enter';
2017
+ if (key === 'space' || key === ' ')
2018
+ return eventKey === ' ' || eventKey === 'spacebar';
2019
+ return eventKey === key;
2020
+ }
2021
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: HotkeyService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable });
2022
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: HotkeyService, providedIn: 'root' });
2023
+ }
2024
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: HotkeyService, decorators: [{
2025
+ type: Injectable,
2026
+ args: [{ providedIn: 'root' }]
2027
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
2028
+ type: Inject,
2029
+ args: [PLATFORM_ID]
2030
+ }] }] });
2031
+
1648
2032
  /*
1649
2033
  * Public API Surface of @xosue-utils/services
1650
2034
  */
@@ -1653,5 +2037,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImpo
1653
2037
  * Generated bundle index. Do not edit.
1654
2038
  */
1655
2039
 
1656
- export { AiAutofillOverlayService, AiAutofillOverlayServiceComponent, AlertService, AlertServiceComponent, AudioPlayerComponent, BrowserNotificationService, ContextMenuService, ContextMenuServiceComponent, FieldHelpTooltipComponent, GlobalLoadingService, GlobalLoadingServiceComponent, ImageViewerComponent, LucideIconComponent, MediaViewerService, MediaViewerServiceComponent, NavigationLoadingService, NavigationLoadingServiceComponent, SessionRequestService, SessionRequestServiceComponent, ToastService, ToastServiceComponent, VideoPlayerComponent, XOSUE_UTILS_SERVICES_CONFIG, XOSUE_UTILS_SERVICES_DEFAULTS, provideXosueUtilsServices, resolveXosueUtilsServicesConfig };
2040
+ export { ActionSheetService, ActionSheetServiceComponent, AiAutofillOverlayService, AiAutofillOverlayServiceComponent, AlertService, AlertServiceComponent, AudioPlayerComponent, BrowserNotificationService, ContextMenuService, ContextMenuServiceComponent, CopyFeedbackService, FieldHelpTooltipComponent, GlobalLoadingService, GlobalLoadingServiceComponent, HotkeyService, ImageViewerComponent, LucideIconComponent, MediaViewerService, MediaViewerServiceComponent, NavigationLoadingService, NavigationLoadingServiceComponent, NetworkStatusService, NetworkStatusServiceComponent, ScrollLockService, SessionRequestService, SessionRequestServiceComponent, ToastService, ToastServiceComponent, VideoPlayerComponent, XOSUE_UTILS_SERVICES_CONFIG, XOSUE_UTILS_SERVICES_DEFAULTS, provideXosueUtilsServices, resolveXosueUtilsServicesConfig };
1657
2041
  //# sourceMappingURL=xosue-utils-services.mjs.map