valtech-components 2.0.833 → 2.0.834

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.
@@ -53,7 +53,7 @@ import 'prismjs/components/prism-json';
53
53
  * Current version of valtech-components.
54
54
  * This is automatically updated during the publish process.
55
55
  */
56
- const VERSION = '2.0.833';
56
+ const VERSION = '2.0.834';
57
57
 
58
58
  /**
59
59
  * Servicio para gestionar presets de componentes.
@@ -4342,6 +4342,16 @@ const VALTECH_DEFAULT_CONTENT = {
4342
4342
  passwordChangedSuccess: '¡Contraseña actualizada!',
4343
4343
  errorCurrentPasswordWrong: 'La contraseña actual es incorrecta',
4344
4344
  errorSamePassword: 'La nueva contraseña debe ser diferente a la actual',
4345
+ // Set password (cuenta OAuth-only)
4346
+ setPasswordTitle: 'Crear contraseña',
4347
+ setPasswordDescription: 'Tu cuenta usa inicio de sesión social. Crea una contraseña para entrar también con tu correo.',
4348
+ setPasswordSubmit: 'Crear contraseña',
4349
+ setPasswordConfirmTitle: '¿Crear contraseña?',
4350
+ setPasswordConfirmMessage: 'Podrás iniciar sesión con tu correo y contraseña, además de tu cuenta social. No perderás el acceso social.',
4351
+ setPasswordConfirmOk: 'Crear contraseña',
4352
+ setPasswordConfirmCancel: 'Cancelar',
4353
+ passwordSetSuccess: '¡Contraseña creada!',
4354
+ errorPasswordAlreadySet: 'Esta cuenta ya tiene una contraseña.',
4345
4355
  // Legal
4346
4356
  legalPrefix: 'Utilizamos los servicios de',
4347
4357
  legalSuffix: 'para ofrecerte una experiencia segura. Al iniciar sesión, aceptas nuestros',
@@ -4432,6 +4442,16 @@ const VALTECH_DEFAULT_CONTENT = {
4432
4442
  passwordChangedSuccess: 'Password updated!',
4433
4443
  errorCurrentPasswordWrong: 'Current password is incorrect',
4434
4444
  errorSamePassword: 'New password must be different from the current one',
4445
+ // Set password (OAuth-only account)
4446
+ setPasswordTitle: 'Create password',
4447
+ setPasswordDescription: 'Your account uses social sign-in. Create a password to also sign in with your email.',
4448
+ setPasswordSubmit: 'Create password',
4449
+ setPasswordConfirmTitle: 'Create password?',
4450
+ setPasswordConfirmMessage: 'You will be able to sign in with your email and password, in addition to your social account. You will not lose social access.',
4451
+ setPasswordConfirmOk: 'Create password',
4452
+ setPasswordConfirmCancel: 'Cancel',
4453
+ passwordSetSuccess: 'Password created!',
4454
+ errorPasswordAlreadySet: 'This account already has a password.',
4435
4455
  // Legal
4436
4456
  legalPrefix: 'We use the services of',
4437
4457
  legalSuffix: 'to offer you a secure experience. By signing in, you accept our',
@@ -20518,7 +20538,7 @@ function isPublicEndpoint(request, authPrefix) {
20518
20538
  '/logout',
20519
20539
  '/mfa/verify', // Solo durante login
20520
20540
  ];
20521
- return publicEndpoints.some((endpoint) => request.url.includes(`${authPrefix}${endpoint}`));
20541
+ return publicEndpoints.some(endpoint => request.url.includes(`${authPrefix}${endpoint}`));
20522
20542
  }
20523
20543
  /**
20524
20544
  * Verifica si la request es a un endpoint que NO debe reintentar refresh en 401.
@@ -20527,6 +20547,9 @@ function isNoRefreshEndpoint(request, authPrefix) {
20527
20547
  // Endpoints que no deben intentar refresh en 401:
20528
20548
  // - Públicos: signin, signup, refresh, logout
20529
20549
  // - MFA: verify (durante login), confirm (401 = código incorrecto), disable (401 = contraseña incorrecta)
20550
+ // - change-password: 401 = contraseña actual incorrecta, NO token expirado.
20551
+ // Sin esta exclusión, el retry tras refresh vuelve a dar 401 y el
20552
+ // catchError de handle401Error dispara logout.
20530
20553
  const noRefreshEndpoints = [
20531
20554
  '/signin',
20532
20555
  '/signup',
@@ -20535,8 +20558,9 @@ function isNoRefreshEndpoint(request, authPrefix) {
20535
20558
  '/mfa/verify',
20536
20559
  '/mfa/confirm',
20537
20560
  '/mfa/disable',
20561
+ '/change-password',
20538
20562
  ];
20539
- return noRefreshEndpoints.some((endpoint) => request.url.includes(`${authPrefix}${endpoint}`));
20563
+ return noRefreshEndpoints.some(endpoint => request.url.includes(`${authPrefix}${endpoint}`));
20540
20564
  }
20541
20565
  /**
20542
20566
  * Maneja errores 401 refrescando el token.
@@ -20545,10 +20569,10 @@ function handle401Error(request, next, authService) {
20545
20569
  if (!isRefreshing) {
20546
20570
  isRefreshing = true;
20547
20571
  refreshTokenSubject.next(null);
20548
- return authService.refreshAccessToken().pipe(switchMap((response) => {
20572
+ return authService.refreshAccessToken().pipe(switchMap(response => {
20549
20573
  refreshTokenSubject.next(response.accessToken);
20550
20574
  return next(addAuthHeader(request, response.accessToken));
20551
- }), catchError((error) => {
20575
+ }), catchError(error => {
20552
20576
  authService.logout();
20553
20577
  return throwError(() => error);
20554
20578
  }), finalize(() => {
@@ -20556,7 +20580,7 @@ function handle401Error(request, next, authService) {
20556
20580
  }));
20557
20581
  }
20558
20582
  // Esperar a que termine el refresco en curso
20559
- return refreshTokenSubject.pipe(filter((token) => token !== null), take(1), switchMap((token) => next(addAuthHeader(request, token))));
20583
+ return refreshTokenSubject.pipe(filter((token) => token !== null), take(1), switchMap(token => next(addAuthHeader(request, token))));
20560
20584
  }
20561
20585
 
20562
20586
  /**
@@ -28924,16 +28948,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
28924
28948
  }], ctorParameters: () => [{ type: i2.ToastController }] });
28925
28949
 
28926
28950
  /**
28927
- * `val-change-password-modal` — modal para que un usuario autenticado cambie
28928
- * su contraseña. Análogo al modal de "recuperar contraseña" del `val-login`,
28929
- * pero para una sesión activa (vista de seguridad / settings).
28951
+ * `val-change-password-modal` — modal de gestión de contraseña para un usuario
28952
+ * autenticado. Análogo al modal de "recuperar contraseña" del `val-login`.
28930
28953
  *
28931
- * Self-contained: inyecta `AuthService` y llama `changePassword()` directo
28932
- * la app solo controla la visibilidad y reacciona a los eventos.
28954
+ * Es dual-mode: al abrirse consulta `AuthService.checkHasPassword()` y se adapta:
28955
+ * - **change** el user ya tiene contraseña pide actual + nueva →
28956
+ * `changePassword()`.
28957
+ * - **set** — el user es OAuth-only (sin contraseña) → pide solo la nueva,
28958
+ * pide confirmación explícita (no pierde el acceso social, es aditivo) →
28959
+ * `setPasswordForOAuthUser()`.
28933
28960
  *
28934
- * El gate de apertura lo controla el padre vía `[isOpen]`. El componente emite
28935
- * `changed` al éxito y `dismissed` cuando el user cierra el modal — el padre
28936
- * pone `isOpen` en `false` en ambos casos.
28961
+ * Self-contained: inyecta `AuthService` y llama el endpoint directo la app
28962
+ * solo controla `[isOpen]` y reacciona a `(changed)` / `(dismissed)`.
28937
28963
  *
28938
28964
  * i18n: usa el namespace compartido `_auth` (mismas claves que `val-login`).
28939
28965
  *
@@ -28947,10 +28973,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
28947
28973
  * ```
28948
28974
  */
28949
28975
  class ChangePasswordModalComponent {
28976
+ /**
28977
+ * Controla la visibilidad del modal. Lo decide el componente padre. Cada vez
28978
+ * que pasa de cerrado a abierto se resuelve el modo (change vs set).
28979
+ */
28980
+ set isOpen(value) {
28981
+ const opening = value && !this._isOpen;
28982
+ this._isOpen = value;
28983
+ if (opening) {
28984
+ this.resolveMode();
28985
+ }
28986
+ }
28987
+ get isOpen() {
28988
+ return this._isOpen;
28989
+ }
28950
28990
  constructor() {
28951
- /** Controla la visibilidad del modal. Lo decide el componente padre. */
28952
- this.isOpen = false;
28953
- /** Emite al cambiar la contraseña con éxito. El padre debe cerrar el modal. */
28991
+ this._isOpen = false;
28992
+ /** Emite al cambiar/crear la contraseña con éxito. El padre cierra el modal. */
28954
28993
  this.changed = new EventEmitter();
28955
28994
  /** Emite cuando el user cierra el modal (botón X o backdrop). */
28956
28995
  this.dismissed = new EventEmitter();
@@ -28958,65 +28997,112 @@ class ChangePasswordModalComponent {
28958
28997
  this.toast = inject(ToastService);
28959
28998
  this.i18n = inject(I18nService);
28960
28999
  this.i18nHelper = inject(InputI18nHelper);
29000
+ this.confirmDialog = inject(ConfirmationDialogService);
29001
+ this._mode = signal('loading');
29002
+ /** Modo actual — `loading` mientras se consulta `checkHasPassword()`. */
29003
+ this.mode = this._mode.asReadonly();
28961
29004
  this._formState = signal(ComponentStates.ENABLED);
28962
- this.formProps = computed(() => this.i18nHelper.resolveForm({
28963
- nameKey: 'changePasswordTitle',
28964
- i18nNamespace: '_auth',
28965
- sections: [
28966
- {
28967
- name: this.t('changePasswordDescription'),
28968
- order: 0,
28969
- fields: [
29005
+ this.formProps = computed(() => {
29006
+ if (this._mode() === 'set') {
29007
+ return this.i18nHelper.resolveForm({
29008
+ nameKey: 'setPasswordTitle',
29009
+ i18nNamespace: '_auth',
29010
+ sections: [
28970
29011
  {
28971
- type: InputType.PASSWORD,
28972
- name: 'currentPassword',
28973
- token: 'change-current-password',
28974
- labelKey: 'currentPassword',
28975
- hint: '',
28976
- placeholderKey: 'passwordPlaceholder',
28977
- errorKeys: {
28978
- required: 'currentPasswordRequired',
28979
- },
28980
- validators: [Validators.required],
29012
+ name: this.t('setPasswordDescription'),
28981
29013
  order: 0,
28982
- state: ComponentStates.ENABLED,
28983
- },
28984
- {
28985
- type: InputType.PASSWORD,
28986
- name: 'newPassword',
28987
- token: 'change-new-password',
28988
- labelKey: 'newPassword',
28989
- hintKey: 'newPasswordHint',
28990
- placeholderKey: 'passwordPlaceholder',
28991
- errorKeys: {
28992
- required: 'passwordRequired',
28993
- minlength: 'passwordMinLength',
28994
- },
28995
- validators: [Validators.required, Validators.minLength(8)],
28996
- order: 1,
28997
- state: ComponentStates.ENABLED,
29014
+ fields: [this.newPasswordField(0)],
28998
29015
  },
28999
29016
  ],
29017
+ actions: {
29018
+ ...SolidDefaultBlock('', 'submit'),
29019
+ token: 'set-submit',
29020
+ textKey: 'setPasswordSubmit',
29021
+ },
29022
+ state: this._formState(),
29023
+ });
29024
+ }
29025
+ return this.i18nHelper.resolveForm({
29026
+ nameKey: 'changePasswordTitle',
29027
+ i18nNamespace: '_auth',
29028
+ sections: [
29029
+ {
29030
+ name: this.t('changePasswordDescription'),
29031
+ order: 0,
29032
+ fields: [
29033
+ {
29034
+ type: InputType.PASSWORD,
29035
+ name: 'currentPassword',
29036
+ token: 'change-current-password',
29037
+ labelKey: 'currentPassword',
29038
+ hint: '',
29039
+ placeholderKey: 'passwordPlaceholder',
29040
+ errorKeys: {
29041
+ required: 'currentPasswordRequired',
29042
+ },
29043
+ validators: [Validators.required],
29044
+ order: 0,
29045
+ state: ComponentStates.ENABLED,
29046
+ },
29047
+ this.newPasswordField(1),
29048
+ ],
29049
+ },
29050
+ ],
29051
+ actions: {
29052
+ ...SolidDefaultBlock('', 'submit'),
29053
+ token: 'change-submit',
29054
+ textKey: 'changePasswordSubmit',
29000
29055
  },
29001
- ],
29002
- actions: {
29003
- ...SolidDefaultBlock('', 'submit'),
29004
- token: 'change-submit',
29005
- textKey: 'changePasswordSubmit',
29006
- },
29007
- state: this._formState(),
29008
- }));
29056
+ state: this._formState(),
29057
+ });
29058
+ });
29009
29059
  addIcons({ closeOutline });
29010
29060
  }
29011
29061
  /** Traduce una clave del namespace `_auth`. */
29012
29062
  t(key) {
29013
29063
  return this.i18n.t(key, '_auth');
29014
29064
  }
29065
+ /** Campo "nueva contraseña" — compartido por ambos modos. */
29066
+ newPasswordField(order) {
29067
+ return {
29068
+ type: InputType.PASSWORD,
29069
+ name: 'newPassword',
29070
+ token: 'change-new-password',
29071
+ labelKey: 'newPassword',
29072
+ hintKey: 'newPasswordHint',
29073
+ placeholderKey: 'passwordPlaceholder',
29074
+ errorKeys: {
29075
+ required: 'passwordRequired',
29076
+ minlength: 'passwordMinLength',
29077
+ },
29078
+ validators: [Validators.required, Validators.minLength(8)],
29079
+ order,
29080
+ state: ComponentStates.ENABLED,
29081
+ };
29082
+ }
29015
29083
  /** Cierre iniciado por el user (X / backdrop). */
29016
29084
  close() {
29017
29085
  this.dismissed.emit();
29018
29086
  }
29019
- submitHandler(event) {
29087
+ /** Consulta si el user ya tiene contraseña para elegir el modo del modal. */
29088
+ resolveMode() {
29089
+ this._mode.set('loading');
29090
+ this._formState.set(ComponentStates.ENABLED);
29091
+ this.auth.checkHasPassword().subscribe({
29092
+ next: res => this._mode.set(res.hasPassword ? 'change' : 'set'),
29093
+ // Fallback conservador: ante un fallo, asumir flujo de cambio normal.
29094
+ error: () => this._mode.set('change'),
29095
+ });
29096
+ }
29097
+ async submitHandler(event) {
29098
+ if (this._mode() === 'set') {
29099
+ await this.handleSetPassword(event);
29100
+ return;
29101
+ }
29102
+ this.handleChangePassword(event);
29103
+ }
29104
+ /** Flujo normal: el user tiene contraseña y la cambia. */
29105
+ handleChangePassword(event) {
29020
29106
  const currentPassword = event.fields['currentPassword'];
29021
29107
  const newPassword = event.fields['newPassword'];
29022
29108
  if (!currentPassword || !newPassword) {
@@ -29036,6 +29122,39 @@ class ChangePasswordModalComponent {
29036
29122
  },
29037
29123
  });
29038
29124
  }
29125
+ /**
29126
+ * Flujo OAuth-only: el user no tiene contraseña. Antes de crearla pide
29127
+ * confirmación explícita — es un cambio de cuenta, aunque aditivo (conserva
29128
+ * el acceso social).
29129
+ */
29130
+ async handleSetPassword(event) {
29131
+ const newPassword = event.fields['newPassword'];
29132
+ if (!newPassword) {
29133
+ this.showToast(this.t('completeAllFields'));
29134
+ return;
29135
+ }
29136
+ const result = await this.confirmDialog.confirm({
29137
+ title: this.t('setPasswordConfirmTitle'),
29138
+ message: this.t('setPasswordConfirmMessage'),
29139
+ confirmButton: { text: this.t('setPasswordConfirmOk'), role: 'confirm' },
29140
+ cancelButton: { text: this.t('setPasswordConfirmCancel'), role: 'cancel' },
29141
+ });
29142
+ if (!result.confirmed) {
29143
+ return;
29144
+ }
29145
+ this._formState.set(ComponentStates.WORKING);
29146
+ this.auth.setPasswordForOAuthUser(newPassword).subscribe({
29147
+ next: () => {
29148
+ this._formState.set(ComponentStates.ENABLED);
29149
+ this.showToast(this.t('passwordSetSuccess'));
29150
+ this.changed.emit();
29151
+ },
29152
+ error: err => {
29153
+ this._formState.set(ComponentStates.ENABLED);
29154
+ this.showToast(this.resolveError(err));
29155
+ },
29156
+ });
29157
+ }
29039
29158
  /** Mapea los códigos de error del backend a mensajes del namespace `_auth`. */
29040
29159
  resolveError(err) {
29041
29160
  const code = err?.code;
@@ -29044,6 +29163,8 @@ class ChangePasswordModalComponent {
29044
29163
  return this.t('errorCurrentPasswordWrong');
29045
29164
  case 'AUTHV2_SAME_PASSWORD':
29046
29165
  return this.t('errorSamePassword');
29166
+ case 'AUTHV2_PASSWORD_ALREADY_SET':
29167
+ return this.t('errorPasswordAlreadySet');
29047
29168
  default:
29048
29169
  return this.t('errorGeneric');
29049
29170
  }
@@ -29052,11 +29173,11 @@ class ChangePasswordModalComponent {
29052
29173
  this.toast.show({ message, duration: 3500 });
29053
29174
  }
29054
29175
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChangePasswordModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
29055
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ChangePasswordModalComponent, isStandalone: true, selector: "val-change-password-modal", inputs: { isOpen: "isOpen" }, outputs: { changed: "changed", dismissed: "dismissed" }, ngImport: i0, template: "<ion-modal [isOpen]=\"isOpen\" (didDismiss)=\"close()\">\n <ng-template>\n <ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"end\">\n <ion-button fill=\"clear\" (click)=\"close()\">\n <ion-icon name=\"close-outline\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content class=\"ion-padding\">\n <section class=\"modal-form-section\">\n <val-form [props]=\"formProps()\" (onSubmit)=\"submitHandler($event)\" />\n </section>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [".modal-form-section{display:flex;flex-direction:column;gap:16px;max-width:420px;margin:0 auto;padding-top:8px}\n"], dependencies: [{ 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: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonModal, selector: "ion-modal" }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }] }); }
29176
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ChangePasswordModalComponent, isStandalone: true, selector: "val-change-password-modal", inputs: { isOpen: "isOpen" }, outputs: { changed: "changed", dismissed: "dismissed" }, ngImport: i0, template: "<ion-modal [isOpen]=\"isOpen\" (didDismiss)=\"close()\">\n <ng-template>\n <ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"end\">\n <ion-button fill=\"clear\" (click)=\"close()\">\n <ion-icon name=\"close-outline\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content class=\"ion-padding\">\n <section class=\"modal-form-section\">\n @if (mode() === 'loading') {\n <div class=\"modal-loading\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @else {\n <val-form [props]=\"formProps()\" (onSubmit)=\"submitHandler($event)\" />\n }\n </section>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [".modal-form-section{display:flex;flex-direction:column;gap:16px;max-width:420px;margin:0 auto;padding-top:8px}.modal-loading{display:flex;justify-content:center;padding:40px 0}\n"], dependencies: [{ 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: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonModal, selector: "ion-modal" }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }] }); }
29056
29177
  }
29057
29178
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChangePasswordModalComponent, decorators: [{
29058
29179
  type: Component,
29059
- args: [{ selector: 'val-change-password-modal', standalone: true, imports: [IonButton, IonButtons, IonContent, IonHeader, IonIcon, IonModal, IonToolbar, FormComponent], template: "<ion-modal [isOpen]=\"isOpen\" (didDismiss)=\"close()\">\n <ng-template>\n <ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"end\">\n <ion-button fill=\"clear\" (click)=\"close()\">\n <ion-icon name=\"close-outline\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content class=\"ion-padding\">\n <section class=\"modal-form-section\">\n <val-form [props]=\"formProps()\" (onSubmit)=\"submitHandler($event)\" />\n </section>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [".modal-form-section{display:flex;flex-direction:column;gap:16px;max-width:420px;margin:0 auto;padding-top:8px}\n"] }]
29180
+ args: [{ selector: 'val-change-password-modal', standalone: true, imports: [IonButton, IonButtons, IonContent, IonHeader, IonIcon, IonModal, IonSpinner, IonToolbar, FormComponent], template: "<ion-modal [isOpen]=\"isOpen\" (didDismiss)=\"close()\">\n <ng-template>\n <ion-header>\n <ion-toolbar>\n <ion-buttons slot=\"end\">\n <ion-button fill=\"clear\" (click)=\"close()\">\n <ion-icon name=\"close-outline\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content class=\"ion-padding\">\n <section class=\"modal-form-section\">\n @if (mode() === 'loading') {\n <div class=\"modal-loading\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @else {\n <val-form [props]=\"formProps()\" (onSubmit)=\"submitHandler($event)\" />\n }\n </section>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [".modal-form-section{display:flex;flex-direction:column;gap:16px;max-width:420px;margin:0 auto;padding-top:8px}.modal-loading{display:flex;justify-content:center;padding:40px 0}\n"] }]
29060
29181
  }], ctorParameters: () => [], propDecorators: { isOpen: [{
29061
29182
  type: Input
29062
29183
  }], changed: [{