valtech-components 2.0.676 → 2.0.678

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/esm2022/lib/components/molecules/image-crop/image-crop.component.mjs +174 -0
  2. package/esm2022/lib/components/molecules/image-crop/index.mjs +2 -0
  3. package/esm2022/lib/components/organisms/avatar-upload/avatar-upload.component.mjs +345 -0
  4. package/esm2022/lib/components/organisms/avatar-upload/types.mjs +15 -0
  5. package/esm2022/lib/components/organisms/bottom-nav/bottom-nav.component.mjs +5 -3
  6. package/esm2022/lib/components/organisms/bottom-nav/types.mjs +2 -1
  7. package/esm2022/lib/services/auth/auth.service.mjs +11 -2
  8. package/esm2022/lib/services/auth/types.mjs +1 -1
  9. package/esm2022/lib/services/image/image.service.mjs +244 -0
  10. package/esm2022/lib/services/image/index.mjs +3 -0
  11. package/esm2022/lib/services/image/types.mjs +13 -0
  12. package/esm2022/lib/version.mjs +2 -2
  13. package/esm2022/public-api.mjs +7 -1
  14. package/fesm2022/valtech-components.mjs +788 -6
  15. package/fesm2022/valtech-components.mjs.map +1 -1
  16. package/lib/components/molecules/image-crop/image-crop.component.d.ts +59 -0
  17. package/lib/components/molecules/image-crop/index.d.ts +1 -0
  18. package/lib/components/organisms/avatar-upload/avatar-upload.component.d.ts +82 -0
  19. package/lib/components/organisms/avatar-upload/types.d.ts +62 -0
  20. package/lib/components/organisms/bottom-nav/bottom-nav.component.d.ts +1 -0
  21. package/lib/components/organisms/bottom-nav/types.d.ts +2 -0
  22. package/lib/services/auth/auth.service.d.ts +6 -1
  23. package/lib/services/auth/types.d.ts +18 -0
  24. package/lib/services/image/image.service.d.ts +76 -0
  25. package/lib/services/image/index.d.ts +2 -0
  26. package/lib/services/image/types.d.ts +74 -0
  27. package/lib/version.d.ts +1 -1
  28. package/package.json +6 -2
  29. package/public-api.d.ts +4 -0
@@ -5,7 +5,7 @@ import { IonAvatar, IonCard, IonIcon, IonButton, IonSpinner, IonText, IonModal,
5
5
  import * as i1 from '@angular/common';
6
6
  import { CommonModule, NgStyle, NgFor, isPlatformBrowser, NgClass } from '@angular/common';
7
7
  import { addIcons } from 'ionicons';
8
- import { addOutline, addCircleOutline, alertOutline, alertCircleOutline, arrowBackOutline, arrowForwardOutline, arrowDownOutline, settings, settingsOutline, checkmarkCircleOutline, ellipsisHorizontalOutline, notifications, notificationsOutline, openOutline, closeOutline, chatbubblesOutline, shareOutline, heart, heartOutline, home, homeOutline, eyeOffOutline, eyeOutline, scanOutline, chevronDownOutline, chevronForwardOutline, checkmarkOutline, clipboardOutline, copyOutline, filterOutline, locationOutline, calendarOutline, businessOutline, logoTwitter, logoInstagram, logoLinkedin, logoYoutube, logoTiktok, logoFacebook, logoGoogle, createOutline, trashOutline, playOutline, refreshOutline, documentTextOutline, lockClosedOutline, informationCircleOutline, logoNpm, removeOutline, add, close, share, create, trash, star, camera, mic, send, downloadOutline, chevronDown, language, globeOutline, checkmark, list, grid, apps, menu, search, person, helpCircle, informationCircle, documentText, mail, calendar, folder, chevronForward, ellipsisHorizontal, chevronBack, playBack, playForward, ellipse, starOutline, starHalf, heartHalf, checkmarkCircle, timeOutline, flag, trendingUp, trendingDown, remove, analytics, people, cash, cart, eye, chatbubbleOutline, thumbsUpOutline, thumbsUp, happyOutline, happy, sadOutline, sad, chevronUp, pin, pencil, callOutline, shuffleOutline, logoWhatsapp, paperPlaneOutline, mailOutline, trophyOutline, ticketOutline, giftOutline, personOutline, ellipsisVertical, closeCircle, alertCircle, logoApple, logoMicrosoft, linkOutline, unlinkOutline, chevronBackOutline, sendOutline, chatbubbleEllipsesOutline, swapVerticalOutline, chevronUpOutline, documentOutline, searchOutline, cartOutline, chatbubble, compass, compassOutline, gridOutline, listOutline, folderOutline, documents, documentsOutline, statsChart, statsChartOutline, bugOutline, bulbOutline, closeCircleOutline, menuOutline } from 'ionicons/icons';
8
+ import { addOutline, addCircleOutline, alertOutline, alertCircleOutline, arrowBackOutline, arrowForwardOutline, arrowDownOutline, settings, settingsOutline, checkmarkCircleOutline, ellipsisHorizontalOutline, notifications, notificationsOutline, openOutline, closeOutline, chatbubblesOutline, shareOutline, heart, heartOutline, home, homeOutline, eyeOffOutline, eyeOutline, scanOutline, chevronDownOutline, chevronForwardOutline, checkmarkOutline, clipboardOutline, copyOutline, filterOutline, locationOutline, calendarOutline, businessOutline, logoTwitter, logoInstagram, logoLinkedin, logoYoutube, logoTiktok, logoFacebook, logoGoogle, createOutline, trashOutline, playOutline, refreshOutline, documentTextOutline, lockClosedOutline, informationCircleOutline, logoNpm, removeOutline, add, close, share, create, trash, star, camera, mic, send, downloadOutline, chevronDown, language, globeOutline, checkmark, list, grid, apps, menu, search, person, helpCircle, informationCircle, documentText, mail, calendar, folder, chevronForward, ellipsisHorizontal, chevronBack, playBack, playForward, ellipse, starOutline, starHalf, heartHalf, checkmarkCircle, timeOutline, flag, trendingUp, trendingDown, remove, analytics, people, cash, cart, eye, chatbubbleOutline, thumbsUpOutline, thumbsUp, happyOutline, happy, sadOutline, sad, chevronUp, pin, pencil, callOutline, shuffleOutline, logoWhatsapp, paperPlaneOutline, mailOutline, trophyOutline, ticketOutline, giftOutline, personOutline, ellipsisVertical, closeCircle, alertCircle, logoApple, logoMicrosoft, linkOutline, unlinkOutline, chevronBackOutline, sendOutline, chatbubbleEllipsesOutline, swapVerticalOutline, chevronUpOutline, documentOutline, searchOutline, cartOutline, chatbubble, compass, compassOutline, gridOutline, listOutline, folderOutline, documents, documentsOutline, statsChart, statsChartOutline, cameraOutline, bugOutline, bulbOutline, closeCircleOutline, menuOutline } from 'ionicons/icons';
9
9
  import * as i1$1 from '@angular/router';
10
10
  import { Router, NavigationEnd, RouterLink, RouterOutlet, RouterModule } from '@angular/router';
11
11
  import { Browser } from '@capacitor/browser';
@@ -39,6 +39,7 @@ import * as i1$7 from '@angular/fire/storage';
39
39
  import { provideStorage, getStorage, connectStorageEmulator, ref, uploadBytesResumable, getDownloadURL, getMetadata, deleteObject, listAll } from '@angular/fire/storage';
40
40
  import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
41
41
  import { filter, map as map$1, catchError as catchError$1, tap, switchMap as switchMap$1, finalize, take, debounceTime as debounceTime$1, takeUntil as takeUntil$1 } from 'rxjs/operators';
42
+ import { ImageCropperComponent } from 'ngx-image-cropper';
42
43
  import * as i1$8 from '@angular/common/http';
43
44
  import { provideHttpClient, withInterceptors, HttpClient } from '@angular/common/http';
44
45
  import { Capacitor } from '@capacitor/core';
@@ -49,7 +50,7 @@ import 'prismjs/components/prism-json';
49
50
  * Current version of valtech-components.
50
51
  * This is automatically updated during the publish process.
51
52
  */
52
- const VERSION = '2.0.676';
53
+ const VERSION = '2.0.678';
53
54
 
54
55
  /**
55
56
  * Servicio para gestionar presets de componentes.
@@ -21196,6 +21197,174 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
21196
21197
  type: Input
21197
21198
  }] } });
21198
21199
 
21200
+ /**
21201
+ * ImageCropComponent
21202
+ *
21203
+ * A modal-ready component for cropping images with a specified aspect ratio.
21204
+ * Uses ngx-image-cropper internally and provides a simple interface.
21205
+ *
21206
+ * @example Inside an ion-modal
21207
+ * ```html
21208
+ * <ion-modal [isOpen]="showCropModal">
21209
+ * <ng-template>
21210
+ * <val-image-crop
21211
+ * [image]="selectedFile"
21212
+ * [aspectRatio]="1"
21213
+ * [roundCropper]="true"
21214
+ * (cropComplete)="onCropComplete($event)"
21215
+ * (cancel)="showCropModal = false"
21216
+ * />
21217
+ * </ng-template>
21218
+ * </ion-modal>
21219
+ * ```
21220
+ */
21221
+ class ImageCropComponent {
21222
+ constructor() {
21223
+ this.i18n = inject(I18nService);
21224
+ /** Image file to crop */
21225
+ this.image = input.required();
21226
+ /** Aspect ratio (1 for square, 16/9 for widescreen, etc.) */
21227
+ this.aspectRatio = input(1);
21228
+ /** Use round cropper (for avatars) */
21229
+ this.roundCropper = input(true);
21230
+ /** Resize output to specific width (0 = no resize) */
21231
+ this.resizeToWidth = input(0);
21232
+ /** i18n namespace for labels */
21233
+ this.i18nNamespace = input('ImageCrop');
21234
+ /** Emitted when crop is confirmed with the cropped blob */
21235
+ this.cropComplete = new EventEmitter();
21236
+ /** Emitted when user cancels the crop */
21237
+ this.cancel = new EventEmitter();
21238
+ /** Emitted when image fails to load */
21239
+ this.loadFailed = new EventEmitter();
21240
+ /** Internal signal for cropped blob */
21241
+ this.croppedBlob = signal(null);
21242
+ /** Computed text for cancel button */
21243
+ this.cancelText = computed(() => {
21244
+ this.i18n.lang(); // Track language changes
21245
+ return this.i18n.t('cancel', 'Common') || 'Cancelar';
21246
+ });
21247
+ /** Computed text for confirm button */
21248
+ this.confirmText = computed(() => {
21249
+ this.i18n.lang();
21250
+ return this.i18n.t('confirm', 'Common') || 'Confirmar';
21251
+ });
21252
+ /** Computed text for title */
21253
+ this.titleText = computed(() => {
21254
+ this.i18n.lang();
21255
+ return this.i18n.t('cropImage', this.i18nNamespace()) || 'Recortar imagen';
21256
+ });
21257
+ }
21258
+ /** Handle crop event from ngx-image-cropper */
21259
+ onImageCropped(event) {
21260
+ if (event.blob) {
21261
+ this.croppedBlob.set(event.blob);
21262
+ }
21263
+ }
21264
+ /** Confirm and emit the cropped blob */
21265
+ confirmCrop() {
21266
+ const blob = this.croppedBlob();
21267
+ if (blob) {
21268
+ this.cropComplete.emit(blob);
21269
+ }
21270
+ }
21271
+ /** Handle load failure */
21272
+ onLoadFailed() {
21273
+ this.loadFailed.emit();
21274
+ }
21275
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ImageCropComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
21276
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: ImageCropComponent, isStandalone: true, selector: "val-image-crop", inputs: { image: { classPropertyName: "image", publicName: "image", isSignal: true, isRequired: true, transformFunction: null }, aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null }, roundCropper: { classPropertyName: "roundCropper", publicName: "roundCropper", isSignal: true, isRequired: false, transformFunction: null }, resizeToWidth: { classPropertyName: "resizeToWidth", publicName: "resizeToWidth", isSignal: true, isRequired: false, transformFunction: null }, i18nNamespace: { classPropertyName: "i18nNamespace", publicName: "i18nNamespace", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cropComplete: "cropComplete", cancel: "cancel", loadFailed: "loadFailed" }, ngImport: i0, template: `
21277
+ <ion-header>
21278
+ <ion-toolbar>
21279
+ <ion-buttons slot="start">
21280
+ <ion-button (click)="cancel.emit()" color="medium">
21281
+ {{ cancelText() }}
21282
+ </ion-button>
21283
+ </ion-buttons>
21284
+ <ion-title>{{ titleText() }}</ion-title>
21285
+ <ion-buttons slot="end">
21286
+ <ion-button
21287
+ (click)="confirmCrop()"
21288
+ color="primary"
21289
+ [strong]="true"
21290
+ [disabled]="!croppedBlob()"
21291
+ >
21292
+ {{ confirmText() }}
21293
+ </ion-button>
21294
+ </ion-buttons>
21295
+ </ion-toolbar>
21296
+ </ion-header>
21297
+
21298
+ <ion-content class="image-crop-content">
21299
+ <image-cropper
21300
+ [imageFile]="image()"
21301
+ [aspectRatio]="aspectRatio()"
21302
+ [maintainAspectRatio]="true"
21303
+ [roundCropper]="roundCropper()"
21304
+ [resizeToWidth]="resizeToWidth()"
21305
+ format="jpeg"
21306
+ outputType="blob"
21307
+ (imageCropped)="onImageCropped($event)"
21308
+ (loadImageFailed)="onLoadFailed()"
21309
+ />
21310
+ </ion-content>
21311
+ `, isInline: true, styles: [".image-crop-content{--background: var(--ion-color-dark)}image-cropper{--cropper-outline-color: rgba(255, 255, 255, .3);--cropper-background-color: var(--ion-color-dark)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: ImageCropperComponent, selector: "image-cropper", inputs: ["imageChangedEvent", "imageURL", "imageBase64", "imageFile", "imageAltText", "options", "cropperFrameAriaLabel", "output", "format", "autoCrop", "cropper", "transform", "maintainAspectRatio", "aspectRatio", "resetCropOnAspectRatioChange", "resizeToWidth", "resizeToHeight", "cropperMinWidth", "cropperMinHeight", "cropperMaxHeight", "cropperMaxWidth", "cropperStaticWidth", "cropperStaticHeight", "canvasRotation", "initialStepSize", "roundCropper", "onlyScaleDown", "imageQuality", "backgroundColor", "containWithinAspectRatio", "hideResizeSquares", "allowMoveImage", "checkImageType", "alignImage", "disabled", "hidden"], outputs: ["imageCropped", "startCropImage", "imageLoaded", "cropperReady", "loadImageFailed", "transformChange", "cropperChange"] }] }); }
21312
+ }
21313
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ImageCropComponent, decorators: [{
21314
+ type: Component,
21315
+ args: [{ selector: 'val-image-crop', standalone: true, imports: [
21316
+ CommonModule,
21317
+ IonHeader,
21318
+ IonToolbar,
21319
+ IonTitle,
21320
+ IonButtons,
21321
+ IonButton,
21322
+ IonContent,
21323
+ ImageCropperComponent,
21324
+ ], template: `
21325
+ <ion-header>
21326
+ <ion-toolbar>
21327
+ <ion-buttons slot="start">
21328
+ <ion-button (click)="cancel.emit()" color="medium">
21329
+ {{ cancelText() }}
21330
+ </ion-button>
21331
+ </ion-buttons>
21332
+ <ion-title>{{ titleText() }}</ion-title>
21333
+ <ion-buttons slot="end">
21334
+ <ion-button
21335
+ (click)="confirmCrop()"
21336
+ color="primary"
21337
+ [strong]="true"
21338
+ [disabled]="!croppedBlob()"
21339
+ >
21340
+ {{ confirmText() }}
21341
+ </ion-button>
21342
+ </ion-buttons>
21343
+ </ion-toolbar>
21344
+ </ion-header>
21345
+
21346
+ <ion-content class="image-crop-content">
21347
+ <image-cropper
21348
+ [imageFile]="image()"
21349
+ [aspectRatio]="aspectRatio()"
21350
+ [maintainAspectRatio]="true"
21351
+ [roundCropper]="roundCropper()"
21352
+ [resizeToWidth]="resizeToWidth()"
21353
+ format="jpeg"
21354
+ outputType="blob"
21355
+ (imageCropped)="onImageCropped($event)"
21356
+ (loadImageFailed)="onLoadFailed()"
21357
+ />
21358
+ </ion-content>
21359
+ `, styles: [".image-crop-content{--background: var(--ion-color-dark)}image-cropper{--cropper-outline-color: rgba(255, 255, 255, .3);--cropper-background-color: var(--ion-color-dark)}\n"] }]
21360
+ }], propDecorators: { cropComplete: [{
21361
+ type: Output
21362
+ }], cancel: [{
21363
+ type: Output
21364
+ }], loadFailed: [{
21365
+ type: Output
21366
+ }] } });
21367
+
21199
21368
  /**
21200
21369
  * Configuración de espaciado predefinida
21201
21370
  */
@@ -30585,6 +30754,15 @@ class AuthService {
30585
30754
  .put(`${this.baseUrl}/profile`, request)
30586
30755
  .pipe(catchError$1(error => this.handleAuthError(error)));
30587
30756
  }
30757
+ /**
30758
+ * Actualiza el avatar del usuario en el backend.
30759
+ * Nota: El estado local del avatar se maneja a través de getProfile().
30760
+ */
30761
+ updateAvatar(request) {
30762
+ return this.http
30763
+ .put(`${this.baseUrl}/profile/avatar`, request)
30764
+ .pipe(catchError$1(error => this.handleAuthError(error)));
30765
+ }
30588
30766
  // =============================================
30589
30767
  // RECUPERACIÓN DE CONTRASEÑA
30590
30768
  // =============================================
@@ -30707,7 +30885,7 @@ class AuthService {
30707
30885
  */
30708
30886
  updateHandle(handle) {
30709
30887
  return this.http
30710
- .put(`${this.baseUrl}/handle`, { handle })
30888
+ .put(`${this.baseUrl}/profile/handle`, { handle })
30711
30889
  .pipe(catchError$1(error => this.handleAuthError(error)));
30712
30890
  }
30713
30891
  /**
@@ -33142,6 +33320,7 @@ const BOTTOM_NAV_DEFAULTS = {
33142
33320
  borderRadius: '16px 16px 0 0',
33143
33321
  elevated: false,
33144
33322
  translucent: true,
33323
+ floating: false,
33145
33324
  },
33146
33325
  };
33147
33326
 
@@ -33358,6 +33537,7 @@ class BottomNavComponent {
33358
33537
  class="bottom-nav"
33359
33538
  [class.bottom-nav--elevated]="config().theme.elevated"
33360
33539
  [class.bottom-nav--translucent]="config().theme.translucent"
33540
+ [class.bottom-nav--floating]="config().theme.floating"
33361
33541
  [class.bottom-nav--safe-area]="config().safeArea"
33362
33542
  [class.bottom-nav--hide-labels]="config().hideLabels"
33363
33543
  [ngStyle]="getThemeStyles()"
@@ -33420,7 +33600,7 @@ class BottomNavComponent {
33420
33600
  }
33421
33601
  </div>
33422
33602
  </nav>
33423
- `, isInline: true, styles: [":host{display:block;position:fixed;bottom:0;left:0;right:0;z-index:100;pointer-events:none}.bottom-nav{--bottom-nav-bg: var(--ion-background-color);--bottom-nav-active: var(--ion-color-primary);--bottom-nav-inactive: var(--ion-color-medium);--bottom-nav-radius: 16px 16px 0 0;--bottom-nav-height: 64px;--bottom-nav-fab-size: 56px;--fab-color: var(--ion-color-primary);pointer-events:auto;background:var(--bottom-nav-bg);border-radius:var(--bottom-nav-radius);height:var(--bottom-nav-height);padding:0 8px}.bottom-nav--elevated{box-shadow:0 -4px 20px #00000014}.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 255, 255, 255),.75);backdrop-filter:blur(20px) saturate(180%);-webkit-backdrop-filter:blur(20px) saturate(180%);border-top:1px solid rgba(var(--ion-text-color-rgb, 0, 0, 0),.06)}.bottom-nav--safe-area{padding-bottom:env(safe-area-inset-bottom,0);height:calc(var(--bottom-nav-height) + env(safe-area-inset-bottom,0))}.bottom-nav__container{display:flex;align-items:center;justify-content:space-around;height:var(--bottom-nav-height);max-width:500px;margin:0 auto}.bottom-nav__tab{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;height:100%;padding:8px 4px;background:transparent;border:none;cursor:pointer;position:relative;overflow:hidden;color:var(--bottom-nav-inactive);transition:color .2s ease;max-width:80px;--ripple-color: var(--bottom-nav-active)}.bottom-nav__tab:focus-visible{outline:2px solid var(--bottom-nav-active);outline-offset:-2px;border-radius:8px}.bottom-nav__tab--active{color:var(--bottom-nav-active)}.bottom-nav__tab--disabled{opacity:.4;cursor:not-allowed;pointer-events:none}.bottom-nav__tab[data-animation=scale] .bottom-nav__tab-icon{transition:transform .2s cubic-bezier(.4,0,.2,1)}.bottom-nav__tab[data-animation=scale].bottom-nav__tab--active .bottom-nav__tab-icon{transform:scale(1.15)}.bottom-nav__tab[data-animation=fade]{transition:opacity .2s ease,color .2s ease}.bottom-nav__tab[data-animation=fade]:not(.bottom-nav__tab--active){opacity:.6}.bottom-nav__tab[data-animation=slide] .bottom-nav__tab-label{transition:transform .2s ease,opacity .2s ease;transform:translateY(6px);opacity:0}.bottom-nav__tab[data-animation=slide].bottom-nav__tab--active .bottom-nav__tab-label{transform:translateY(0);opacity:1}.bottom-nav__tab-icon{position:relative;font-size:24px;line-height:1}.bottom-nav__tab-icon ion-icon{display:block}.bottom-nav__tab-label{font-size:11px;font-weight:500;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;letter-spacing:.01em}.bottom-nav__badge{position:absolute;top:-4px;right:-10px;min-width:18px;height:18px;padding:0 5px;font-size:10px;font-weight:600;line-height:18px;text-align:center;color:#fff;background-color:var(--ion-color-danger);border-radius:9px;box-shadow:0 1px 3px #0003}.bottom-nav__badge--dot{min-width:10px;width:10px;height:10px;padding:0;top:-2px;right:-4px;border-radius:50%}.bottom-nav__fab{position:relative;flex-shrink:0;width:var(--bottom-nav-fab-size);height:var(--bottom-nav-fab-size);margin:0 12px;margin-top:calc(var(--bottom-nav-fab-size) * -.35);background:var(--fab-color);border:none;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#fff;font-size:28px;box-shadow:0 4px 14px rgba(var(--ion-color-primary-rgb, 56, 128, 255),.4);transition:transform .2s cubic-bezier(.4,0,.2,1),box-shadow .2s ease;overflow:hidden;--ripple-color: rgba(255, 255, 255, .3)}.bottom-nav__fab:hover{transform:scale(1.08);box-shadow:0 6px 20px rgba(var(--ion-color-primary-rgb, 56, 128, 255),.5)}.bottom-nav__fab:active{transform:scale(.95)}.bottom-nav__fab:focus-visible{outline:3px solid white;outline-offset:2px}.bottom-nav__fab--small{--bottom-nav-fab-size: 48px;font-size:24px;margin-top:-14.4px}.bottom-nav__fab ion-icon{display:block}.bottom-nav--hide-labels{--bottom-nav-height: 56px}.bottom-nav--hide-labels .bottom-nav__tab-label{display:none}.bottom-nav--hide-labels .bottom-nav__tab-icon{font-size:26px}@media (prefers-color-scheme: dark){.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 0, 0, 0),.8);border-top-color:#ffffff14}.bottom-nav--elevated{box-shadow:0 -4px 20px #00000040}.bottom-nav__fab{box-shadow:0 4px 14px #0006}.bottom-nav__fab:hover{box-shadow:0 6px 20px #00000080}}:host-context(.dark) .bottom-nav--translucent,:host-context(body.dark) .bottom-nav--translucent,:host-context([data-theme=dark]) .bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 18, 18, 18),.85);border-top-color:#ffffff14}:host-context(.dark) .bottom-nav--elevated,:host-context(body.dark) .bottom-nav--elevated,:host-context([data-theme=dark]) .bottom-nav--elevated{box-shadow:0 -4px 20px #0000004d}:host-context(.dark) .bottom-nav__fab,:host-context(body.dark) .bottom-nav__fab,:host-context([data-theme=dark]) .bottom-nav__fab{box-shadow:0 4px 14px #00000080}:host-context(.dark) .bottom-nav__fab:hover,:host-context(body.dark) .bottom-nav__fab:hover,:host-context([data-theme=dark]) .bottom-nav__fab:hover{box-shadow:0 6px 20px #0009}@supports (padding-bottom: env(safe-area-inset-bottom)){.bottom-nav--safe-area .bottom-nav__container{height:var(--bottom-nav-height)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonRippleEffect, selector: "ion-ripple-effect", inputs: ["type"] }] }); }
33603
+ `, isInline: true, styles: [":host{display:block;position:fixed;bottom:0;left:0;right:0;z-index:100;pointer-events:none}.bottom-nav{--bottom-nav-bg: var(--ion-background-color);--bottom-nav-active: var(--ion-color-primary);--bottom-nav-inactive: var(--ion-color-medium);--bottom-nav-radius: 16px 16px 0 0;--bottom-nav-height: 64px;--bottom-nav-fab-size: 56px;--fab-color: var(--ion-color-primary);pointer-events:auto;background:var(--bottom-nav-bg);border-radius:var(--bottom-nav-radius);height:var(--bottom-nav-height);padding:0 8px}.bottom-nav--elevated{box-shadow:0 -4px 20px #00000014}.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 255, 255, 255),.75);backdrop-filter:blur(20px) saturate(180%);-webkit-backdrop-filter:blur(20px) saturate(180%);border-top:1px solid rgba(var(--ion-text-color-rgb, 0, 0, 0),.06)}.bottom-nav--floating{margin:0 16px 16px;border-radius:28px;border-top:none;box-shadow:0 4px 24px #00000014,0 8px 32px #0000000a;--bottom-nav-radius: 28px}.bottom-nav--floating.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 255, 255, 255),.82);backdrop-filter:blur(28px) saturate(200%);-webkit-backdrop-filter:blur(28px) saturate(200%);border:1px solid rgba(var(--ion-text-color-rgb, 0, 0, 0),.04)}.bottom-nav--floating.bottom-nav--elevated{box-shadow:0 4px 24px #0000001a,0 12px 48px #00000014}.bottom-nav--floating.bottom-nav--safe-area{margin-bottom:calc(16px + env(safe-area-inset-bottom,0))}.bottom-nav--safe-area{padding-bottom:env(safe-area-inset-bottom,0);height:calc(var(--bottom-nav-height) + env(safe-area-inset-bottom,0))}.bottom-nav__container{display:flex;align-items:center;justify-content:space-around;height:var(--bottom-nav-height);max-width:500px;margin:0 auto}.bottom-nav__tab{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;height:100%;padding:8px 4px;background:transparent;border:none;cursor:pointer;position:relative;overflow:hidden;color:var(--bottom-nav-inactive);transition:color .2s ease;max-width:80px;--ripple-color: var(--bottom-nav-active)}.bottom-nav__tab:focus-visible{outline:2px solid var(--bottom-nav-active);outline-offset:-2px;border-radius:8px}.bottom-nav__tab--active{color:var(--bottom-nav-active)}.bottom-nav__tab--disabled{opacity:.4;cursor:not-allowed;pointer-events:none}.bottom-nav__tab[data-animation=scale] .bottom-nav__tab-icon{transition:transform .2s cubic-bezier(.4,0,.2,1)}.bottom-nav__tab[data-animation=scale].bottom-nav__tab--active .bottom-nav__tab-icon{transform:scale(1.15)}.bottom-nav__tab[data-animation=fade]{transition:opacity .2s ease,color .2s ease}.bottom-nav__tab[data-animation=fade]:not(.bottom-nav__tab--active){opacity:.6}.bottom-nav__tab[data-animation=slide] .bottom-nav__tab-label{transition:transform .2s ease,opacity .2s ease;transform:translateY(6px);opacity:0}.bottom-nav__tab[data-animation=slide].bottom-nav__tab--active .bottom-nav__tab-label{transform:translateY(0);opacity:1}.bottom-nav__tab-icon{position:relative;font-size:24px;line-height:1}.bottom-nav__tab-icon ion-icon{display:block}.bottom-nav__tab-label{font-size:11px;font-weight:500;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;letter-spacing:.01em}.bottom-nav__badge{position:absolute;top:-4px;right:-10px;min-width:18px;height:18px;padding:0 5px;font-size:10px;font-weight:600;line-height:18px;text-align:center;color:#fff;background-color:var(--ion-color-danger);border-radius:9px;box-shadow:0 1px 3px #0003}.bottom-nav__badge--dot{min-width:10px;width:10px;height:10px;padding:0;top:-2px;right:-4px;border-radius:50%}.bottom-nav__fab{position:relative;flex-shrink:0;width:var(--bottom-nav-fab-size);height:var(--bottom-nav-fab-size);margin:0 12px;margin-top:calc(var(--bottom-nav-fab-size) * -.35);background:var(--fab-color);border:none;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#fff;font-size:28px;box-shadow:0 4px 14px rgba(var(--ion-color-primary-rgb, 56, 128, 255),.4);transition:transform .2s cubic-bezier(.4,0,.2,1),box-shadow .2s ease;overflow:hidden;--ripple-color: rgba(255, 255, 255, .3)}.bottom-nav__fab:hover{transform:scale(1.08);box-shadow:0 6px 20px rgba(var(--ion-color-primary-rgb, 56, 128, 255),.5)}.bottom-nav__fab:active{transform:scale(.95)}.bottom-nav__fab:focus-visible{outline:3px solid white;outline-offset:2px}.bottom-nav__fab--small{--bottom-nav-fab-size: 48px;font-size:24px;margin-top:-14.4px}.bottom-nav__fab ion-icon{display:block}.bottom-nav--hide-labels{--bottom-nav-height: 56px}.bottom-nav--hide-labels .bottom-nav__tab-label{display:none}.bottom-nav--hide-labels .bottom-nav__tab-icon{font-size:26px}@media (prefers-color-scheme: dark){.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 0, 0, 0),.8);border-top-color:#ffffff14}.bottom-nav--elevated{box-shadow:0 -4px 20px #00000040}.bottom-nav--floating{box-shadow:0 4px 24px #0003,0 8px 32px #00000026}.bottom-nav--floating.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 18, 18, 18),.88);border-color:#ffffff0f}.bottom-nav__fab{box-shadow:0 4px 14px #0006}.bottom-nav__fab:hover{box-shadow:0 6px 20px #00000080}}:host-context(.dark) .bottom-nav--translucent,:host-context(body.dark) .bottom-nav--translucent,:host-context([data-theme=dark]) .bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 18, 18, 18),.85);border-top-color:#ffffff14}:host-context(.dark) .bottom-nav--elevated,:host-context(body.dark) .bottom-nav--elevated,:host-context([data-theme=dark]) .bottom-nav--elevated{box-shadow:0 -4px 20px #0000004d}:host-context(.dark) .bottom-nav--floating,:host-context(body.dark) .bottom-nav--floating,:host-context([data-theme=dark]) .bottom-nav--floating{box-shadow:0 4px 24px #0003,0 8px 32px #00000026}:host-context(.dark) .bottom-nav--floating.bottom-nav--translucent,:host-context(body.dark) .bottom-nav--floating.bottom-nav--translucent,:host-context([data-theme=dark]) .bottom-nav--floating.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 18, 18, 18),.88);border-color:#ffffff0f}:host-context(.dark) .bottom-nav__fab,:host-context(body.dark) .bottom-nav__fab,:host-context([data-theme=dark]) .bottom-nav__fab{box-shadow:0 4px 14px #00000080}:host-context(.dark) .bottom-nav__fab:hover,:host-context(body.dark) .bottom-nav__fab:hover,:host-context([data-theme=dark]) .bottom-nav__fab:hover{box-shadow:0 6px 20px #0009}@supports (padding-bottom: env(safe-area-inset-bottom)){.bottom-nav--safe-area .bottom-nav__container{height:var(--bottom-nav-height)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonRippleEffect, selector: "ion-ripple-effect", inputs: ["type"] }] }); }
33424
33604
  }
33425
33605
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: BottomNavComponent, decorators: [{
33426
33606
  type: Component,
@@ -33429,6 +33609,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
33429
33609
  class="bottom-nav"
33430
33610
  [class.bottom-nav--elevated]="config().theme.elevated"
33431
33611
  [class.bottom-nav--translucent]="config().theme.translucent"
33612
+ [class.bottom-nav--floating]="config().theme.floating"
33432
33613
  [class.bottom-nav--safe-area]="config().safeArea"
33433
33614
  [class.bottom-nav--hide-labels]="config().hideLabels"
33434
33615
  [ngStyle]="getThemeStyles()"
@@ -33491,13 +33672,614 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
33491
33672
  }
33492
33673
  </div>
33493
33674
  </nav>
33494
- `, styles: [":host{display:block;position:fixed;bottom:0;left:0;right:0;z-index:100;pointer-events:none}.bottom-nav{--bottom-nav-bg: var(--ion-background-color);--bottom-nav-active: var(--ion-color-primary);--bottom-nav-inactive: var(--ion-color-medium);--bottom-nav-radius: 16px 16px 0 0;--bottom-nav-height: 64px;--bottom-nav-fab-size: 56px;--fab-color: var(--ion-color-primary);pointer-events:auto;background:var(--bottom-nav-bg);border-radius:var(--bottom-nav-radius);height:var(--bottom-nav-height);padding:0 8px}.bottom-nav--elevated{box-shadow:0 -4px 20px #00000014}.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 255, 255, 255),.75);backdrop-filter:blur(20px) saturate(180%);-webkit-backdrop-filter:blur(20px) saturate(180%);border-top:1px solid rgba(var(--ion-text-color-rgb, 0, 0, 0),.06)}.bottom-nav--safe-area{padding-bottom:env(safe-area-inset-bottom,0);height:calc(var(--bottom-nav-height) + env(safe-area-inset-bottom,0))}.bottom-nav__container{display:flex;align-items:center;justify-content:space-around;height:var(--bottom-nav-height);max-width:500px;margin:0 auto}.bottom-nav__tab{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;height:100%;padding:8px 4px;background:transparent;border:none;cursor:pointer;position:relative;overflow:hidden;color:var(--bottom-nav-inactive);transition:color .2s ease;max-width:80px;--ripple-color: var(--bottom-nav-active)}.bottom-nav__tab:focus-visible{outline:2px solid var(--bottom-nav-active);outline-offset:-2px;border-radius:8px}.bottom-nav__tab--active{color:var(--bottom-nav-active)}.bottom-nav__tab--disabled{opacity:.4;cursor:not-allowed;pointer-events:none}.bottom-nav__tab[data-animation=scale] .bottom-nav__tab-icon{transition:transform .2s cubic-bezier(.4,0,.2,1)}.bottom-nav__tab[data-animation=scale].bottom-nav__tab--active .bottom-nav__tab-icon{transform:scale(1.15)}.bottom-nav__tab[data-animation=fade]{transition:opacity .2s ease,color .2s ease}.bottom-nav__tab[data-animation=fade]:not(.bottom-nav__tab--active){opacity:.6}.bottom-nav__tab[data-animation=slide] .bottom-nav__tab-label{transition:transform .2s ease,opacity .2s ease;transform:translateY(6px);opacity:0}.bottom-nav__tab[data-animation=slide].bottom-nav__tab--active .bottom-nav__tab-label{transform:translateY(0);opacity:1}.bottom-nav__tab-icon{position:relative;font-size:24px;line-height:1}.bottom-nav__tab-icon ion-icon{display:block}.bottom-nav__tab-label{font-size:11px;font-weight:500;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;letter-spacing:.01em}.bottom-nav__badge{position:absolute;top:-4px;right:-10px;min-width:18px;height:18px;padding:0 5px;font-size:10px;font-weight:600;line-height:18px;text-align:center;color:#fff;background-color:var(--ion-color-danger);border-radius:9px;box-shadow:0 1px 3px #0003}.bottom-nav__badge--dot{min-width:10px;width:10px;height:10px;padding:0;top:-2px;right:-4px;border-radius:50%}.bottom-nav__fab{position:relative;flex-shrink:0;width:var(--bottom-nav-fab-size);height:var(--bottom-nav-fab-size);margin:0 12px;margin-top:calc(var(--bottom-nav-fab-size) * -.35);background:var(--fab-color);border:none;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#fff;font-size:28px;box-shadow:0 4px 14px rgba(var(--ion-color-primary-rgb, 56, 128, 255),.4);transition:transform .2s cubic-bezier(.4,0,.2,1),box-shadow .2s ease;overflow:hidden;--ripple-color: rgba(255, 255, 255, .3)}.bottom-nav__fab:hover{transform:scale(1.08);box-shadow:0 6px 20px rgba(var(--ion-color-primary-rgb, 56, 128, 255),.5)}.bottom-nav__fab:active{transform:scale(.95)}.bottom-nav__fab:focus-visible{outline:3px solid white;outline-offset:2px}.bottom-nav__fab--small{--bottom-nav-fab-size: 48px;font-size:24px;margin-top:-14.4px}.bottom-nav__fab ion-icon{display:block}.bottom-nav--hide-labels{--bottom-nav-height: 56px}.bottom-nav--hide-labels .bottom-nav__tab-label{display:none}.bottom-nav--hide-labels .bottom-nav__tab-icon{font-size:26px}@media (prefers-color-scheme: dark){.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 0, 0, 0),.8);border-top-color:#ffffff14}.bottom-nav--elevated{box-shadow:0 -4px 20px #00000040}.bottom-nav__fab{box-shadow:0 4px 14px #0006}.bottom-nav__fab:hover{box-shadow:0 6px 20px #00000080}}:host-context(.dark) .bottom-nav--translucent,:host-context(body.dark) .bottom-nav--translucent,:host-context([data-theme=dark]) .bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 18, 18, 18),.85);border-top-color:#ffffff14}:host-context(.dark) .bottom-nav--elevated,:host-context(body.dark) .bottom-nav--elevated,:host-context([data-theme=dark]) .bottom-nav--elevated{box-shadow:0 -4px 20px #0000004d}:host-context(.dark) .bottom-nav__fab,:host-context(body.dark) .bottom-nav__fab,:host-context([data-theme=dark]) .bottom-nav__fab{box-shadow:0 4px 14px #00000080}:host-context(.dark) .bottom-nav__fab:hover,:host-context(body.dark) .bottom-nav__fab:hover,:host-context([data-theme=dark]) .bottom-nav__fab:hover{box-shadow:0 6px 20px #0009}@supports (padding-bottom: env(safe-area-inset-bottom)){.bottom-nav--safe-area .bottom-nav__container{height:var(--bottom-nav-height)}}\n"] }]
33675
+ `, styles: [":host{display:block;position:fixed;bottom:0;left:0;right:0;z-index:100;pointer-events:none}.bottom-nav{--bottom-nav-bg: var(--ion-background-color);--bottom-nav-active: var(--ion-color-primary);--bottom-nav-inactive: var(--ion-color-medium);--bottom-nav-radius: 16px 16px 0 0;--bottom-nav-height: 64px;--bottom-nav-fab-size: 56px;--fab-color: var(--ion-color-primary);pointer-events:auto;background:var(--bottom-nav-bg);border-radius:var(--bottom-nav-radius);height:var(--bottom-nav-height);padding:0 8px}.bottom-nav--elevated{box-shadow:0 -4px 20px #00000014}.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 255, 255, 255),.75);backdrop-filter:blur(20px) saturate(180%);-webkit-backdrop-filter:blur(20px) saturate(180%);border-top:1px solid rgba(var(--ion-text-color-rgb, 0, 0, 0),.06)}.bottom-nav--floating{margin:0 16px 16px;border-radius:28px;border-top:none;box-shadow:0 4px 24px #00000014,0 8px 32px #0000000a;--bottom-nav-radius: 28px}.bottom-nav--floating.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 255, 255, 255),.82);backdrop-filter:blur(28px) saturate(200%);-webkit-backdrop-filter:blur(28px) saturate(200%);border:1px solid rgba(var(--ion-text-color-rgb, 0, 0, 0),.04)}.bottom-nav--floating.bottom-nav--elevated{box-shadow:0 4px 24px #0000001a,0 12px 48px #00000014}.bottom-nav--floating.bottom-nav--safe-area{margin-bottom:calc(16px + env(safe-area-inset-bottom,0))}.bottom-nav--safe-area{padding-bottom:env(safe-area-inset-bottom,0);height:calc(var(--bottom-nav-height) + env(safe-area-inset-bottom,0))}.bottom-nav__container{display:flex;align-items:center;justify-content:space-around;height:var(--bottom-nav-height);max-width:500px;margin:0 auto}.bottom-nav__tab{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;height:100%;padding:8px 4px;background:transparent;border:none;cursor:pointer;position:relative;overflow:hidden;color:var(--bottom-nav-inactive);transition:color .2s ease;max-width:80px;--ripple-color: var(--bottom-nav-active)}.bottom-nav__tab:focus-visible{outline:2px solid var(--bottom-nav-active);outline-offset:-2px;border-radius:8px}.bottom-nav__tab--active{color:var(--bottom-nav-active)}.bottom-nav__tab--disabled{opacity:.4;cursor:not-allowed;pointer-events:none}.bottom-nav__tab[data-animation=scale] .bottom-nav__tab-icon{transition:transform .2s cubic-bezier(.4,0,.2,1)}.bottom-nav__tab[data-animation=scale].bottom-nav__tab--active .bottom-nav__tab-icon{transform:scale(1.15)}.bottom-nav__tab[data-animation=fade]{transition:opacity .2s ease,color .2s ease}.bottom-nav__tab[data-animation=fade]:not(.bottom-nav__tab--active){opacity:.6}.bottom-nav__tab[data-animation=slide] .bottom-nav__tab-label{transition:transform .2s ease,opacity .2s ease;transform:translateY(6px);opacity:0}.bottom-nav__tab[data-animation=slide].bottom-nav__tab--active .bottom-nav__tab-label{transform:translateY(0);opacity:1}.bottom-nav__tab-icon{position:relative;font-size:24px;line-height:1}.bottom-nav__tab-icon ion-icon{display:block}.bottom-nav__tab-label{font-size:11px;font-weight:500;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;letter-spacing:.01em}.bottom-nav__badge{position:absolute;top:-4px;right:-10px;min-width:18px;height:18px;padding:0 5px;font-size:10px;font-weight:600;line-height:18px;text-align:center;color:#fff;background-color:var(--ion-color-danger);border-radius:9px;box-shadow:0 1px 3px #0003}.bottom-nav__badge--dot{min-width:10px;width:10px;height:10px;padding:0;top:-2px;right:-4px;border-radius:50%}.bottom-nav__fab{position:relative;flex-shrink:0;width:var(--bottom-nav-fab-size);height:var(--bottom-nav-fab-size);margin:0 12px;margin-top:calc(var(--bottom-nav-fab-size) * -.35);background:var(--fab-color);border:none;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#fff;font-size:28px;box-shadow:0 4px 14px rgba(var(--ion-color-primary-rgb, 56, 128, 255),.4);transition:transform .2s cubic-bezier(.4,0,.2,1),box-shadow .2s ease;overflow:hidden;--ripple-color: rgba(255, 255, 255, .3)}.bottom-nav__fab:hover{transform:scale(1.08);box-shadow:0 6px 20px rgba(var(--ion-color-primary-rgb, 56, 128, 255),.5)}.bottom-nav__fab:active{transform:scale(.95)}.bottom-nav__fab:focus-visible{outline:3px solid white;outline-offset:2px}.bottom-nav__fab--small{--bottom-nav-fab-size: 48px;font-size:24px;margin-top:-14.4px}.bottom-nav__fab ion-icon{display:block}.bottom-nav--hide-labels{--bottom-nav-height: 56px}.bottom-nav--hide-labels .bottom-nav__tab-label{display:none}.bottom-nav--hide-labels .bottom-nav__tab-icon{font-size:26px}@media (prefers-color-scheme: dark){.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 0, 0, 0),.8);border-top-color:#ffffff14}.bottom-nav--elevated{box-shadow:0 -4px 20px #00000040}.bottom-nav--floating{box-shadow:0 4px 24px #0003,0 8px 32px #00000026}.bottom-nav--floating.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 18, 18, 18),.88);border-color:#ffffff0f}.bottom-nav__fab{box-shadow:0 4px 14px #0006}.bottom-nav__fab:hover{box-shadow:0 6px 20px #00000080}}:host-context(.dark) .bottom-nav--translucent,:host-context(body.dark) .bottom-nav--translucent,:host-context([data-theme=dark]) .bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 18, 18, 18),.85);border-top-color:#ffffff14}:host-context(.dark) .bottom-nav--elevated,:host-context(body.dark) .bottom-nav--elevated,:host-context([data-theme=dark]) .bottom-nav--elevated{box-shadow:0 -4px 20px #0000004d}:host-context(.dark) .bottom-nav--floating,:host-context(body.dark) .bottom-nav--floating,:host-context([data-theme=dark]) .bottom-nav--floating{box-shadow:0 4px 24px #0003,0 8px 32px #00000026}:host-context(.dark) .bottom-nav--floating.bottom-nav--translucent,:host-context(body.dark) .bottom-nav--floating.bottom-nav--translucent,:host-context([data-theme=dark]) .bottom-nav--floating.bottom-nav--translucent{background:rgba(var(--ion-background-color-rgb, 18, 18, 18),.88);border-color:#ffffff0f}:host-context(.dark) .bottom-nav__fab,:host-context(body.dark) .bottom-nav__fab,:host-context([data-theme=dark]) .bottom-nav__fab{box-shadow:0 4px 14px #00000080}:host-context(.dark) .bottom-nav__fab:hover,:host-context(body.dark) .bottom-nav__fab:hover,:host-context([data-theme=dark]) .bottom-nav__fab:hover{box-shadow:0 6px 20px #0009}@supports (padding-bottom: env(safe-area-inset-bottom)){.bottom-nav--safe-area .bottom-nav__container{height:var(--bottom-nav-height)}}\n"] }]
33495
33676
  }], propDecorators: { tabClick: [{
33496
33677
  type: Output
33497
33678
  }], fabClick: [{
33498
33679
  type: Output
33499
33680
  }] } });
33500
33681
 
33682
+ /**
33683
+ * Default values for image processing
33684
+ */
33685
+ const IMAGE_DEFAULTS = {
33686
+ maxWidth: 800,
33687
+ maxHeight: 800,
33688
+ quality: 0.8,
33689
+ mimeType: 'image/jpeg',
33690
+ maxSize: 10 * 1024 * 1024, // 10MB
33691
+ allowedTypes: ['image/jpeg', 'image/png', 'image/webp', 'image/gif'],
33692
+ thumbnailSize: 150,
33693
+ };
33694
+
33695
+ /**
33696
+ * ImageService
33697
+ *
33698
+ * Service for image processing including compression, thumbnails, cropping and validation.
33699
+ * Uses HTML Canvas for all operations - no external dependencies.
33700
+ *
33701
+ * @example
33702
+ * ```typescript
33703
+ * const imageService = inject(ImageService);
33704
+ *
33705
+ * // Compress an image
33706
+ * const compressed = await imageService.compress(file, { maxWidth: 800, quality: 0.8 });
33707
+ *
33708
+ * // Generate thumbnail
33709
+ * const thumb = await imageService.thumbnail(file, 150);
33710
+ *
33711
+ * // Validate before processing
33712
+ * const validation = imageService.validate(file, { maxSize: 5 * 1024 * 1024 });
33713
+ * if (!validation.valid) {
33714
+ * console.error(validation.message);
33715
+ * }
33716
+ * ```
33717
+ */
33718
+ class ImageService {
33719
+ /**
33720
+ * Compress an image maintaining aspect ratio
33721
+ * @param file - File or Blob to compress
33722
+ * @param options - Compression options
33723
+ * @returns Promise with processed image data
33724
+ */
33725
+ async compress(file, options) {
33726
+ const opts = {
33727
+ maxWidth: options?.maxWidth ?? IMAGE_DEFAULTS.maxWidth,
33728
+ maxHeight: options?.maxHeight ?? IMAGE_DEFAULTS.maxHeight,
33729
+ quality: options?.quality ?? IMAGE_DEFAULTS.quality,
33730
+ mimeType: options?.mimeType ?? IMAGE_DEFAULTS.mimeType,
33731
+ };
33732
+ const img = await this.loadImage(file);
33733
+ const { width, height } = this.calculateDimensions(img.width, img.height, opts.maxWidth, opts.maxHeight);
33734
+ const canvas = document.createElement('canvas');
33735
+ canvas.width = width;
33736
+ canvas.height = height;
33737
+ const ctx = canvas.getContext('2d');
33738
+ ctx.drawImage(img, 0, 0, width, height);
33739
+ const blob = await this.canvasToBlob(canvas, opts.mimeType, opts.quality);
33740
+ const dataUrl = canvas.toDataURL(opts.mimeType, opts.quality);
33741
+ return {
33742
+ blob,
33743
+ dataUrl,
33744
+ width,
33745
+ height,
33746
+ size: blob.size,
33747
+ };
33748
+ }
33749
+ /**
33750
+ * Generate a square thumbnail from an image
33751
+ * @param file - File or Blob to process
33752
+ * @param size - Thumbnail size in pixels (default: 150)
33753
+ * @returns Promise with processed thumbnail
33754
+ */
33755
+ async thumbnail(file, size) {
33756
+ const thumbSize = size ?? IMAGE_DEFAULTS.thumbnailSize;
33757
+ const img = await this.loadImage(file);
33758
+ // Calculate square crop from center
33759
+ const minDim = Math.min(img.width, img.height);
33760
+ const cropX = (img.width - minDim) / 2;
33761
+ const cropY = (img.height - minDim) / 2;
33762
+ const canvas = document.createElement('canvas');
33763
+ canvas.width = thumbSize;
33764
+ canvas.height = thumbSize;
33765
+ const ctx = canvas.getContext('2d');
33766
+ ctx.drawImage(img, cropX, cropY, minDim, minDim, 0, 0, thumbSize, thumbSize);
33767
+ const blob = await this.canvasToBlob(canvas, IMAGE_DEFAULTS.mimeType, 0.7 // Lower quality for thumbnails
33768
+ );
33769
+ const dataUrl = canvas.toDataURL(IMAGE_DEFAULTS.mimeType, 0.7);
33770
+ return {
33771
+ blob,
33772
+ dataUrl,
33773
+ width: thumbSize,
33774
+ height: thumbSize,
33775
+ size: blob.size,
33776
+ };
33777
+ }
33778
+ /**
33779
+ * Crop an image with specific coordinates
33780
+ * @param file - File or Blob to crop
33781
+ * @param cropData - Crop coordinates and dimensions
33782
+ * @param options - Optional compression options for output
33783
+ * @returns Promise with cropped image
33784
+ */
33785
+ async crop(file, cropData, options) {
33786
+ const img = await this.loadImage(file);
33787
+ const opts = {
33788
+ quality: options?.quality ?? IMAGE_DEFAULTS.quality,
33789
+ mimeType: options?.mimeType ?? IMAGE_DEFAULTS.mimeType,
33790
+ };
33791
+ const canvas = document.createElement('canvas');
33792
+ canvas.width = cropData.width;
33793
+ canvas.height = cropData.height;
33794
+ const ctx = canvas.getContext('2d');
33795
+ ctx.drawImage(img, cropData.x, cropData.y, cropData.width, cropData.height, 0, 0, cropData.width, cropData.height);
33796
+ // Apply max dimensions if specified
33797
+ if (options?.maxWidth || options?.maxHeight) {
33798
+ return this.compress(await this.canvasToBlob(canvas, opts.mimeType, 1), options);
33799
+ }
33800
+ const blob = await this.canvasToBlob(canvas, opts.mimeType, opts.quality);
33801
+ const dataUrl = canvas.toDataURL(opts.mimeType, opts.quality);
33802
+ return {
33803
+ blob,
33804
+ dataUrl,
33805
+ width: cropData.width,
33806
+ height: cropData.height,
33807
+ size: blob.size,
33808
+ };
33809
+ }
33810
+ /**
33811
+ * Validate an image file before processing
33812
+ * @param file - File to validate
33813
+ * @param options - Validation options
33814
+ * @returns Validation result with error details if invalid
33815
+ */
33816
+ validate(file, options) {
33817
+ const opts = {
33818
+ maxSize: options?.maxSize ?? IMAGE_DEFAULTS.maxSize,
33819
+ allowedTypes: options?.allowedTypes ?? IMAGE_DEFAULTS.allowedTypes,
33820
+ };
33821
+ // Check file type
33822
+ if (!opts.allowedTypes.includes(file.type)) {
33823
+ return {
33824
+ valid: false,
33825
+ error: 'invalidType',
33826
+ message: `Formato no válido. Usa: ${opts.allowedTypes.map(t => t.split('/')[1].toUpperCase()).join(', ')}`,
33827
+ };
33828
+ }
33829
+ // Check file size
33830
+ if (file.size > opts.maxSize) {
33831
+ const maxMB = Math.round(opts.maxSize / (1024 * 1024));
33832
+ return {
33833
+ valid: false,
33834
+ error: 'fileTooLarge',
33835
+ message: `La imagen es muy grande. Máximo ${maxMB}MB`,
33836
+ };
33837
+ }
33838
+ return { valid: true };
33839
+ }
33840
+ /**
33841
+ * Validate image dimensions (async - requires loading image)
33842
+ * @param file - File to validate
33843
+ * @param options - Validation options with minWidth/minHeight
33844
+ * @returns Promise with validation result
33845
+ */
33846
+ async validateDimensions(file, options) {
33847
+ const img = await this.loadImage(file);
33848
+ if (options.minWidth && img.width < options.minWidth) {
33849
+ return {
33850
+ valid: false,
33851
+ error: 'imageTooSmall',
33852
+ message: `La imagen debe tener al menos ${options.minWidth}px de ancho`,
33853
+ };
33854
+ }
33855
+ if (options.minHeight && img.height < options.minHeight) {
33856
+ return {
33857
+ valid: false,
33858
+ error: 'imageTooSmall',
33859
+ message: `La imagen debe tener al menos ${options.minHeight}px de alto`,
33860
+ };
33861
+ }
33862
+ return { valid: true };
33863
+ }
33864
+ /**
33865
+ * Convert a Blob/File to a data URL
33866
+ */
33867
+ async toDataUrl(file) {
33868
+ return new Promise((resolve, reject) => {
33869
+ const reader = new FileReader();
33870
+ reader.onload = () => resolve(reader.result);
33871
+ reader.onerror = reject;
33872
+ reader.readAsDataURL(file);
33873
+ });
33874
+ }
33875
+ /**
33876
+ * Convert a data URL to a Blob
33877
+ */
33878
+ dataUrlToBlob(dataUrl) {
33879
+ const arr = dataUrl.split(',');
33880
+ const mime = arr[0].match(/:(.*?);/)[1];
33881
+ const bstr = atob(arr[1]);
33882
+ let n = bstr.length;
33883
+ const u8arr = new Uint8Array(n);
33884
+ while (n--) {
33885
+ u8arr[n] = bstr.charCodeAt(n);
33886
+ }
33887
+ return new Blob([u8arr], { type: mime });
33888
+ }
33889
+ // ============== Private Helpers ==============
33890
+ loadImage(file) {
33891
+ return new Promise((resolve, reject) => {
33892
+ const img = new Image();
33893
+ img.onload = () => {
33894
+ URL.revokeObjectURL(img.src);
33895
+ resolve(img);
33896
+ };
33897
+ img.onerror = reject;
33898
+ img.src = URL.createObjectURL(file);
33899
+ });
33900
+ }
33901
+ calculateDimensions(originalWidth, originalHeight, maxWidth, maxHeight) {
33902
+ let width = originalWidth;
33903
+ let height = originalHeight;
33904
+ // Scale down if necessary, maintaining aspect ratio
33905
+ if (width > maxWidth) {
33906
+ height = (height * maxWidth) / width;
33907
+ width = maxWidth;
33908
+ }
33909
+ if (height > maxHeight) {
33910
+ width = (width * maxHeight) / height;
33911
+ height = maxHeight;
33912
+ }
33913
+ return {
33914
+ width: Math.round(width),
33915
+ height: Math.round(height),
33916
+ };
33917
+ }
33918
+ canvasToBlob(canvas, mimeType, quality) {
33919
+ return new Promise((resolve, reject) => {
33920
+ canvas.toBlob((blob) => {
33921
+ if (blob)
33922
+ resolve(blob);
33923
+ else
33924
+ reject(new Error('Failed to create blob from canvas'));
33925
+ }, mimeType, quality);
33926
+ });
33927
+ }
33928
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ImageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
33929
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ImageService, providedIn: 'root' }); }
33930
+ }
33931
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ImageService, decorators: [{
33932
+ type: Injectable,
33933
+ args: [{ providedIn: 'root' }]
33934
+ }] });
33935
+
33936
+ /**
33937
+ * Default values
33938
+ */
33939
+ const AVATAR_UPLOAD_DEFAULTS = {
33940
+ size: 100,
33941
+ editable: true,
33942
+ storagePath: 'avatars',
33943
+ i18nNamespace: 'AvatarUpload',
33944
+ maxFileSize: 10 * 1024 * 1024, // 10MB
33945
+ compressQuality: 0.8,
33946
+ maxWidth: 800,
33947
+ thumbnailSize: 150,
33948
+ backgroundColor: '#6366f1', // Indigo
33949
+ };
33950
+
33951
+ addIcons({ cameraOutline });
33952
+ /**
33953
+ * AvatarUploadComponent
33954
+ *
33955
+ * A complete avatar upload solution with:
33956
+ * - Image selection from device
33957
+ * - Crop modal with round preview
33958
+ * - Automatic compression and thumbnail generation
33959
+ * - Upload to Firebase Storage
33960
+ * - Backend sync via AuthService
33961
+ *
33962
+ * @example Basic usage
33963
+ * ```html
33964
+ * <val-avatar-upload
33965
+ * [props]="{
33966
+ * currentUrl: user()?.avatarUrl,
33967
+ * initials: 'JD',
33968
+ * size: 120
33969
+ * }"
33970
+ * (uploaded)="onAvatarUploaded($event)"
33971
+ * (error)="onError($event)"
33972
+ * />
33973
+ * ```
33974
+ */
33975
+ class AvatarUploadComponent {
33976
+ constructor() {
33977
+ this.imageService = inject(ImageService);
33978
+ this.storageService = inject(StorageService);
33979
+ this.authService = inject(AuthService);
33980
+ this.i18n = inject(I18nService);
33981
+ /** Component configuration */
33982
+ this.props = input({});
33983
+ /** Emitted after successful upload and backend sync */
33984
+ this.uploaded = new EventEmitter();
33985
+ /** Emitted on any error during the process */
33986
+ this.error = new EventEmitter();
33987
+ /** Emitted when upload starts */
33988
+ this.uploadStart = new EventEmitter();
33989
+ // Internal state
33990
+ this.loading = signal(false);
33991
+ this.showCropModal = signal(false);
33992
+ this.selectedFile = signal(null);
33993
+ this.previewUrl = signal(null);
33994
+ this.imageLoadError = signal(false);
33995
+ /** Merged config with defaults */
33996
+ this.config = computed(() => ({
33997
+ ...AVATAR_UPLOAD_DEFAULTS,
33998
+ ...this.props(),
33999
+ }));
34000
+ /** URL to display (preview takes priority over current) */
34001
+ this.displayUrl = computed(() => {
34002
+ if (this.imageLoadError())
34003
+ return null;
34004
+ return this.previewUrl() || this.config().currentUrl || null;
34005
+ });
34006
+ /** Aria label for edit button */
34007
+ this.editButtonLabel = computed(() => {
34008
+ this.i18n.lang();
34009
+ return this.i18n.t('changePhoto', this.config().i18nNamespace) || 'Cambiar foto';
34010
+ });
34011
+ }
34012
+ /** Open file picker dialog */
34013
+ openFilePicker() {
34014
+ this.fileInput.nativeElement.click();
34015
+ }
34016
+ /** Handle file selection */
34017
+ onFileSelected(event) {
34018
+ const input = event.target;
34019
+ const file = input.files?.[0];
34020
+ if (!file)
34021
+ return;
34022
+ // Reset input for same file selection
34023
+ input.value = '';
34024
+ // Validate file
34025
+ const validation = this.imageService.validate(file, {
34026
+ maxSize: this.config().maxFileSize,
34027
+ allowedTypes: ['image/jpeg', 'image/png', 'image/webp'],
34028
+ });
34029
+ if (!validation.valid) {
34030
+ this.emitError(validation.error, validation.message);
34031
+ return;
34032
+ }
34033
+ // Open crop modal
34034
+ this.selectedFile.set(file);
34035
+ this.showCropModal.set(true);
34036
+ }
34037
+ /** Handle crop completion */
34038
+ async onCropComplete(croppedBlob) {
34039
+ this.showCropModal.set(false);
34040
+ this.selectedFile.set(null);
34041
+ await this.processAndUpload(croppedBlob);
34042
+ }
34043
+ /** Handle crop cancel */
34044
+ onCropCancel() {
34045
+ this.showCropModal.set(false);
34046
+ this.selectedFile.set(null);
34047
+ }
34048
+ /** Handle crop load failure */
34049
+ onCropLoadFailed() {
34050
+ this.showCropModal.set(false);
34051
+ this.selectedFile.set(null);
34052
+ this.emitError('invalidType', this.i18n.t('loadFailed', this.config().i18nNamespace) || 'No se pudo cargar la imagen');
34053
+ }
34054
+ /** Handle image load error */
34055
+ onImageError() {
34056
+ this.imageLoadError.set(true);
34057
+ }
34058
+ /** Process cropped image and upload */
34059
+ async processAndUpload(croppedBlob) {
34060
+ this.loading.set(true);
34061
+ this.uploadStart.emit();
34062
+ try {
34063
+ const config = this.config();
34064
+ // 1. Compress image
34065
+ const compressed = await this.imageService.compress(croppedBlob, {
34066
+ maxWidth: config.maxWidth,
34067
+ maxHeight: config.maxWidth,
34068
+ quality: config.compressQuality,
34069
+ });
34070
+ // 2. Generate thumbnail
34071
+ const thumbnail = await this.imageService.thumbnail(compressed.blob, config.thumbnailSize);
34072
+ // 3. Set preview immediately
34073
+ this.previewUrl.set(compressed.dataUrl);
34074
+ this.imageLoadError.set(false);
34075
+ // 4. Get user ID for storage path
34076
+ const userId = this.authService.user()?.userId;
34077
+ if (!userId) {
34078
+ throw new Error('User not authenticated');
34079
+ }
34080
+ // 5. Upload to Firebase Storage
34081
+ const timestamp = Date.now();
34082
+ const avatarPath = `${config.storagePath}/${userId}/avatar_${timestamp}.jpg`;
34083
+ const thumbPath = `${config.storagePath}/${userId}/thumb_${timestamp}.jpg`;
34084
+ const [avatarResult, thumbResult] = await Promise.all([
34085
+ this.storageService.uploadAndGetUrl(avatarPath, compressed.blob),
34086
+ this.storageService.uploadAndGetUrl(thumbPath, thumbnail.blob),
34087
+ ]);
34088
+ // 6. Update backend
34089
+ await firstValueFrom(this.authService.updateAvatar({
34090
+ avatarUrl: avatarResult.downloadUrl,
34091
+ avatarThumbnail: thumbResult.downloadUrl,
34092
+ }));
34093
+ // 7. Emit success
34094
+ const result = {
34095
+ avatarUrl: avatarResult.downloadUrl,
34096
+ thumbnailUrl: thumbResult.downloadUrl,
34097
+ };
34098
+ this.uploaded.emit(result);
34099
+ }
34100
+ catch (err) {
34101
+ // Revert preview on error
34102
+ this.previewUrl.set(null);
34103
+ const message = err instanceof Error
34104
+ ? err.message
34105
+ : this.i18n.t('uploadError', this.config().i18nNamespace) || 'Error al subir la imagen';
34106
+ this.emitError('uploadFailed', message, err);
34107
+ }
34108
+ finally {
34109
+ this.loading.set(false);
34110
+ }
34111
+ }
34112
+ /** Emit error event */
34113
+ emitError(type, message, originalError) {
34114
+ this.error.emit({ type, message, originalError });
34115
+ }
34116
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AvatarUploadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
34117
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AvatarUploadComponent, isStandalone: true, selector: "val-avatar-upload", inputs: { props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { uploaded: "uploaded", error: "error", uploadStart: "uploadStart" }, viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: `
34118
+ <div
34119
+ class="avatar-upload"
34120
+ [style.--avatar-size.px]="config().size"
34121
+ [class.avatar-upload--loading]="loading()"
34122
+ >
34123
+ <div class="avatar-container">
34124
+ <!-- Avatar Image or Initials -->
34125
+ @if (displayUrl()) {
34126
+ <img
34127
+ class="avatar-image"
34128
+ [src]="displayUrl()"
34129
+ alt="Avatar"
34130
+ (error)="onImageError()"
34131
+ />
34132
+ } @else {
34133
+ <div
34134
+ class="avatar-initials"
34135
+ [style.background-color]="config().backgroundColor"
34136
+ >
34137
+ {{ config().initials || '?' }}
34138
+ </div>
34139
+ }
34140
+
34141
+ <!-- Edit Button -->
34142
+ @if (config().editable && !loading()) {
34143
+ <button
34144
+ class="edit-button"
34145
+ type="button"
34146
+ (click)="openFilePicker()"
34147
+ [attr.aria-label]="editButtonLabel()"
34148
+ >
34149
+ <ion-icon name="camera-outline"></ion-icon>
34150
+ </button>
34151
+ }
34152
+
34153
+ <!-- Loading Overlay -->
34154
+ @if (loading()) {
34155
+ <div class="loading-overlay">
34156
+ <ion-spinner name="crescent"></ion-spinner>
34157
+ </div>
34158
+ }
34159
+ </div>
34160
+
34161
+ <!-- Hidden File Input -->
34162
+ <input
34163
+ #fileInput
34164
+ type="file"
34165
+ accept="image/jpeg,image/png,image/webp"
34166
+ (change)="onFileSelected($event)"
34167
+ hidden
34168
+ />
34169
+
34170
+ <!-- Crop Modal -->
34171
+ <ion-modal
34172
+ [isOpen]="showCropModal()"
34173
+ (didDismiss)="onCropCancel()"
34174
+ [breakpoints]="[0, 1]"
34175
+ [initialBreakpoint]="1"
34176
+ >
34177
+ <ng-template>
34178
+ @if (selectedFile()) {
34179
+ <val-image-crop
34180
+ [image]="selectedFile()!"
34181
+ [aspectRatio]="1"
34182
+ [roundCropper]="true"
34183
+ [i18nNamespace]="config().i18nNamespace"
34184
+ (cropComplete)="onCropComplete($event)"
34185
+ (cancel)="onCropCancel()"
34186
+ (loadFailed)="onCropLoadFailed()"
34187
+ />
34188
+ }
34189
+ </ng-template>
34190
+ </ion-modal>
34191
+ </div>
34192
+ `, isInline: true, styles: [".avatar-upload{--avatar-size: 100px;--edit-button-size: 32px;--edit-button-offset: 4px;display:inline-block}.avatar-container{position:relative;width:var(--avatar-size);height:var(--avatar-size);border-radius:50%;overflow:visible}.avatar-image{width:100%;height:100%;border-radius:50%;object-fit:cover;background-color:var(--ion-color-light)}.avatar-initials{width:100%;height:100%;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:calc(var(--avatar-size) * .4);font-weight:600;color:#fff;text-transform:uppercase;-webkit-user-select:none;user-select:none}.edit-button{position:absolute;bottom:var(--edit-button-offset);right:var(--edit-button-offset);width:var(--edit-button-size);height:var(--edit-button-size);border-radius:50%;border:2px solid white;background:var(--ion-color-primary);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:transform .2s ease,background-color .2s ease;box-shadow:0 2px 8px #00000026}.edit-button ion-icon{font-size:calc(var(--edit-button-size) * .5)}.edit-button:hover{transform:scale(1.1);background:var(--ion-color-primary-shade)}.edit-button:active{transform:scale(.95)}.edit-button:focus-visible{outline:2px solid var(--ion-color-primary);outline-offset:2px}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;border-radius:50%;background:#00000080;display:flex;align-items:center;justify-content:center}.loading-overlay ion-spinner{--color: white;width:calc(var(--avatar-size) * .4);height:calc(var(--avatar-size) * .4)}.avatar-upload--loading .edit-button{display:none}.avatar-upload--loading .avatar-image,.avatar-upload--loading .avatar-initials{filter:brightness(.7)}@container (max-width: 60px){.edit-button{--edit-button-size: 24px;--edit-button-offset: 0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonModal, selector: "ion-modal" }, { kind: "component", type: ImageCropComponent, selector: "val-image-crop", inputs: ["image", "aspectRatio", "roundCropper", "resizeToWidth", "i18nNamespace"], outputs: ["cropComplete", "cancel", "loadFailed"] }] }); }
34193
+ }
34194
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AvatarUploadComponent, decorators: [{
34195
+ type: Component,
34196
+ args: [{ selector: 'val-avatar-upload', standalone: true, imports: [CommonModule, IonIcon, IonSpinner, IonModal, ImageCropComponent], template: `
34197
+ <div
34198
+ class="avatar-upload"
34199
+ [style.--avatar-size.px]="config().size"
34200
+ [class.avatar-upload--loading]="loading()"
34201
+ >
34202
+ <div class="avatar-container">
34203
+ <!-- Avatar Image or Initials -->
34204
+ @if (displayUrl()) {
34205
+ <img
34206
+ class="avatar-image"
34207
+ [src]="displayUrl()"
34208
+ alt="Avatar"
34209
+ (error)="onImageError()"
34210
+ />
34211
+ } @else {
34212
+ <div
34213
+ class="avatar-initials"
34214
+ [style.background-color]="config().backgroundColor"
34215
+ >
34216
+ {{ config().initials || '?' }}
34217
+ </div>
34218
+ }
34219
+
34220
+ <!-- Edit Button -->
34221
+ @if (config().editable && !loading()) {
34222
+ <button
34223
+ class="edit-button"
34224
+ type="button"
34225
+ (click)="openFilePicker()"
34226
+ [attr.aria-label]="editButtonLabel()"
34227
+ >
34228
+ <ion-icon name="camera-outline"></ion-icon>
34229
+ </button>
34230
+ }
34231
+
34232
+ <!-- Loading Overlay -->
34233
+ @if (loading()) {
34234
+ <div class="loading-overlay">
34235
+ <ion-spinner name="crescent"></ion-spinner>
34236
+ </div>
34237
+ }
34238
+ </div>
34239
+
34240
+ <!-- Hidden File Input -->
34241
+ <input
34242
+ #fileInput
34243
+ type="file"
34244
+ accept="image/jpeg,image/png,image/webp"
34245
+ (change)="onFileSelected($event)"
34246
+ hidden
34247
+ />
34248
+
34249
+ <!-- Crop Modal -->
34250
+ <ion-modal
34251
+ [isOpen]="showCropModal()"
34252
+ (didDismiss)="onCropCancel()"
34253
+ [breakpoints]="[0, 1]"
34254
+ [initialBreakpoint]="1"
34255
+ >
34256
+ <ng-template>
34257
+ @if (selectedFile()) {
34258
+ <val-image-crop
34259
+ [image]="selectedFile()!"
34260
+ [aspectRatio]="1"
34261
+ [roundCropper]="true"
34262
+ [i18nNamespace]="config().i18nNamespace"
34263
+ (cropComplete)="onCropComplete($event)"
34264
+ (cancel)="onCropCancel()"
34265
+ (loadFailed)="onCropLoadFailed()"
34266
+ />
34267
+ }
34268
+ </ng-template>
34269
+ </ion-modal>
34270
+ </div>
34271
+ `, styles: [".avatar-upload{--avatar-size: 100px;--edit-button-size: 32px;--edit-button-offset: 4px;display:inline-block}.avatar-container{position:relative;width:var(--avatar-size);height:var(--avatar-size);border-radius:50%;overflow:visible}.avatar-image{width:100%;height:100%;border-radius:50%;object-fit:cover;background-color:var(--ion-color-light)}.avatar-initials{width:100%;height:100%;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:calc(var(--avatar-size) * .4);font-weight:600;color:#fff;text-transform:uppercase;-webkit-user-select:none;user-select:none}.edit-button{position:absolute;bottom:var(--edit-button-offset);right:var(--edit-button-offset);width:var(--edit-button-size);height:var(--edit-button-size);border-radius:50%;border:2px solid white;background:var(--ion-color-primary);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:transform .2s ease,background-color .2s ease;box-shadow:0 2px 8px #00000026}.edit-button ion-icon{font-size:calc(var(--edit-button-size) * .5)}.edit-button:hover{transform:scale(1.1);background:var(--ion-color-primary-shade)}.edit-button:active{transform:scale(.95)}.edit-button:focus-visible{outline:2px solid var(--ion-color-primary);outline-offset:2px}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;border-radius:50%;background:#00000080;display:flex;align-items:center;justify-content:center}.loading-overlay ion-spinner{--color: white;width:calc(var(--avatar-size) * .4);height:calc(var(--avatar-size) * .4)}.avatar-upload--loading .edit-button{display:none}.avatar-upload--loading .avatar-image,.avatar-upload--loading .avatar-initials{filter:brightness(.7)}@container (max-width: 60px){.edit-button{--edit-button-size: 24px;--edit-button-offset: 0}}\n"] }]
34272
+ }], propDecorators: { fileInput: [{
34273
+ type: ViewChild,
34274
+ args: ['fileInput']
34275
+ }], uploaded: [{
34276
+ type: Output
34277
+ }], error: [{
34278
+ type: Output
34279
+ }], uploadStart: [{
34280
+ type: Output
34281
+ }] } });
34282
+
33501
34283
  class LayoutComponent {
33502
34284
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
33503
34285
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: LayoutComponent, isStandalone: true, selector: "val-layout", ngImport: i0, template: `
@@ -41106,5 +41888,5 @@ function buildFooterLinks(links, t) {
41106
41888
  * Generated bundle index. Do not edit.
41107
41889
  */
41108
41890
 
41109
- export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_SPACING, AccordionComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AppConfigService, ArticleBuilder, ArticleComponent, AuthBackgroundComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, CheckInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContentLoaderComponent, ContentReactionComponent, CountdownComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_CANCEL_BUTTON, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_LEGEND_LABELS, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PAYMENT_STATUS_COLORS, DEFAULT_PAYMENT_STATUS_LABELS, DEFAULT_PLATFORMS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_STATUS_COLORS, DEFAULT_STATUS_LABELS, DEFAULT_WINNER_LABELS, DataTableComponent, DateInputComponent, DateRangeInputComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DownloadService, EmailInputComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FabComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FooterComponent, FooterLinksComponent, FormComponent, FormFooterComponent, FormSkeletonComponent, FunHeaderComponent, GlowCardComponent, GridSkeletonComponent, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, Icon, IconComponent, IconService, ImageComponent, InAppBrowserService, InfiniteListComponent, InfoComponent, InputI18nHelper, InputType, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LOGIN_DEFAULTS, LanguageSelectorComponent, LayeredCardComponent, LayoutComponent, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginComponent, MODAL_SIZES, MOTION, MaintenancePageComponent, MenuComponent, MessagingService, MetaService, ModalService, MultiSelectSearchComponent, NavigationService, NoContentComponent, NotesBoxComponent, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PLATFORM_CONFIGS, PageContentComponent, PageTemplateComponent, PageWrapperComponent, PaginationComponent, PaginationService, ParticipantCardComponent, PasswordInputComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, RadioInputComponent, RaffleStatusCardComponent, RangeInputComponent, RatingComponent, RecapCardComponent, RefresherComponent, RightsFooterComponent, RotatingTextComponent, SKELETON_PRESETS, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SegmentControlComponent, SelectSearchComponent, SessionService, ShareButtonsComponent, SimpleComponent, SkeletonComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TicketGridComponent, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TranslatePipe, TypedCollection, UpdateBannerComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_DEFAULT_CONTENT, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_SOCIAL_LINKS, VERSION, WinnerDisplayComponent, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, buildFooterLinks, buildPath, collections, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createTitleProps, extractPathParams, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, goToTop, guestGuard, hasEmulators, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, permissionGuard, permissionGuardFromRoute, provideValtechAds, provideValtechAppConfig, provideValtechAuth, provideValtechAuthInterceptor, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechPresets, provideValtechSkeleton, query, replaceSpecialChars, resolveColor, resolveInputDefaultValue, roleGuard, storagePaths, superAdminGuard };
41891
+ export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_SPACING, AVATAR_UPLOAD_DEFAULTS, AccordionComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AppConfigService, ArticleBuilder, ArticleComponent, AuthBackgroundComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, CheckInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContentLoaderComponent, ContentReactionComponent, CountdownComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_CANCEL_BUTTON, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_LEGEND_LABELS, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PAYMENT_STATUS_COLORS, DEFAULT_PAYMENT_STATUS_LABELS, DEFAULT_PLATFORMS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_STATUS_COLORS, DEFAULT_STATUS_LABELS, DEFAULT_WINNER_LABELS, DataTableComponent, DateInputComponent, DateRangeInputComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DownloadService, EmailInputComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FabComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FooterComponent, FooterLinksComponent, FormComponent, FormFooterComponent, FormSkeletonComponent, FunHeaderComponent, GlowCardComponent, GridSkeletonComponent, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoComponent, InputI18nHelper, InputType, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LOGIN_DEFAULTS, LanguageSelectorComponent, LayeredCardComponent, LayoutComponent, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginComponent, MODAL_SIZES, MOTION, MaintenancePageComponent, MenuComponent, MessagingService, MetaService, ModalService, MultiSelectSearchComponent, NavigationService, NoContentComponent, NotesBoxComponent, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PLATFORM_CONFIGS, PageContentComponent, PageTemplateComponent, PageWrapperComponent, PaginationComponent, PaginationService, ParticipantCardComponent, PasswordInputComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, RadioInputComponent, RaffleStatusCardComponent, RangeInputComponent, RatingComponent, RecapCardComponent, RefresherComponent, RightsFooterComponent, RotatingTextComponent, SKELETON_PRESETS, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SegmentControlComponent, SelectSearchComponent, SessionService, ShareButtonsComponent, SimpleComponent, SkeletonComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TicketGridComponent, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TranslatePipe, TypedCollection, UpdateBannerComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_DEFAULT_CONTENT, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_SOCIAL_LINKS, VERSION, WinnerDisplayComponent, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, buildFooterLinks, buildPath, collections, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createTitleProps, extractPathParams, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, goToTop, guestGuard, hasEmulators, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, permissionGuard, permissionGuardFromRoute, provideValtechAds, provideValtechAppConfig, provideValtechAuth, provideValtechAuthInterceptor, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechPresets, provideValtechSkeleton, query, replaceSpecialChars, resolveColor, resolveInputDefaultValue, roleGuard, storagePaths, superAdminGuard };
41110
41892
  //# sourceMappingURL=valtech-components.mjs.map