valtech-components 2.0.904 → 2.0.906
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/lib/components/molecules/content-reaction/content-reaction.component.mjs +4 -3
- package/esm2022/lib/components/molecules/image-crop/image-crop.component.mjs +6 -13
- package/esm2022/lib/components/organisms/avatar-upload/avatar-upload.component.mjs +17 -3
- package/esm2022/lib/services/auth/auth.service.mjs +6 -2
- package/esm2022/lib/services/auth/storage.service.mjs +6 -3
- package/esm2022/lib/services/auth/sync.service.mjs +5 -2
- package/esm2022/lib/services/feedback/feedback.service.mjs +17 -12
- package/esm2022/lib/services/org/org.service.mjs +10 -1
- package/esm2022/lib/version.mjs +2 -2
- package/fesm2022/valtech-components.mjs +59 -27
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/atoms/rights-footer/rights-footer.component.d.ts +1 -1
- package/lib/components/molecules/content-reaction/content-reaction.component.d.ts +1 -0
- package/lib/components/organisms/article/article.component.d.ts +2 -2
- package/lib/components/organisms/toolbar/toolbar.component.d.ts +1 -1
- package/lib/services/feedback/feedback.service.d.ts +2 -0
- package/lib/services/org/org.service.d.ts +31 -0
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, Injectable, makeEnvironmentProviders, APP_INITIALIZER, inject, EventEmitter, Component, Input, Output, input, computed, ChangeDetectionStrategy, HostBinding, PLATFORM_ID, Inject, HostListener, Pipe, effect, ViewChild, ChangeDetectorRef, ContentChild, ErrorHandler, DestroyRef, InjectionToken, Optional, runInInjectionContext, output, Injector, TemplateRef, ViewContainerRef, isSignal, Directive, ElementRef, ViewEncapsulation } from '@angular/core';
|
|
2
|
+
import { signal, Injectable, makeEnvironmentProviders, APP_INITIALIZER, inject, EventEmitter, Component, Input, Output, input, computed, ChangeDetectionStrategy, HostBinding, PLATFORM_ID, Inject, HostListener, Pipe, effect, ViewChild, ChangeDetectorRef, ContentChild, ErrorHandler, DestroyRef, InjectionToken, isDevMode, Optional, runInInjectionContext, output, Injector, TemplateRef, ViewContainerRef, isSignal, Directive, ElementRef, ViewEncapsulation } from '@angular/core';
|
|
3
3
|
import * as i2$1 from '@ionic/angular/standalone';
|
|
4
4
|
import { IonAvatar, IonCard, IonIcon, IonButton, IonSpinner, IonText, IonModal, IonHeader, IonToolbar, IonContent, IonButtons, IonTitle, IonProgressBar, IonSkeletonText, IonFab, IonFabButton, IonFabList, IonLabel, IonCardContent, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCheckbox, IonTextarea, IonDatetime, IonDatetimeButton, IonInput, IonSelect, IonSelectOption, IonPopover, IonList, IonItem, IonRadioGroup, IonRadio, IonRange, IonSearchbar, IonSegment, IonSegmentButton, IonToggle, IonAccordion, IonAccordionGroup, IonTabBar, IonTabButton, IonBadge, IonBreadcrumb, IonBreadcrumbs, IonChip, IonNote, ToastController as ToastController$1, IonCol, IonRow, IonRefresher, IonRefresherContent, IonRippleEffect, AlertController, IonMenuButton, IonFooter, IonListHeader, IonInfiniteScroll, IonInfiniteScrollContent, IonGrid, MenuController, IonMenu, IonMenuToggle, IonSplitPane } from '@ionic/angular/standalone';
|
|
5
5
|
import * as i1 from '@angular/common';
|
|
@@ -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.
|
|
56
|
+
const VERSION = '2.0.906';
|
|
57
57
|
|
|
58
58
|
/**
|
|
59
59
|
* Servicio para gestionar presets de componentes.
|
|
@@ -21912,6 +21912,9 @@ class AuthStorageService {
|
|
|
21912
21912
|
constructor(config, platformId) {
|
|
21913
21913
|
this.config = config;
|
|
21914
21914
|
this.isBrowser = isPlatformBrowser(platformId);
|
|
21915
|
+
if (!this.config && isDevMode()) {
|
|
21916
|
+
console.warn('[valtech-components] AuthStorageService: provideValtechAuth() no está en main.ts.');
|
|
21917
|
+
}
|
|
21915
21918
|
const prefix = this.config?.storagePrefix || 'valtech_auth_';
|
|
21916
21919
|
this.keys = {
|
|
21917
21920
|
ACCESS_TOKEN: `${prefix}access_token`,
|
|
@@ -22049,7 +22052,7 @@ class AuthStorageService {
|
|
|
22049
22052
|
if (!this.isBrowser)
|
|
22050
22053
|
return;
|
|
22051
22054
|
try {
|
|
22052
|
-
Object.values(this.keys).forEach(
|
|
22055
|
+
Object.values(this.keys).forEach(key => localStorage.removeItem(key));
|
|
22053
22056
|
}
|
|
22054
22057
|
catch (e) {
|
|
22055
22058
|
console.warn('[ValtechAuth] Error limpiando storage:', e);
|
|
@@ -22092,6 +22095,9 @@ class AuthSyncService {
|
|
|
22092
22095
|
/** Observable de eventos de sincronización */
|
|
22093
22096
|
this.onEvent$ = this.eventSubject.asObservable();
|
|
22094
22097
|
this.isBrowser = isPlatformBrowser(platformId);
|
|
22098
|
+
if (!this.config && isDevMode()) {
|
|
22099
|
+
console.warn('[valtech-components] AuthSyncService: provideValtechAuth() no está en main.ts.');
|
|
22100
|
+
}
|
|
22095
22101
|
const prefix = this.config?.storagePrefix || 'valtech_auth_';
|
|
22096
22102
|
this.channelName = `${prefix}sync_channel`;
|
|
22097
22103
|
}
|
|
@@ -25800,6 +25806,10 @@ class AuthService {
|
|
|
25800
25806
|
this.mfaPending = this.stateService.mfaPending;
|
|
25801
25807
|
/** Error actual */
|
|
25802
25808
|
this.error = this.stateService.error;
|
|
25809
|
+
if (!this.config && isDevMode()) {
|
|
25810
|
+
console.warn("[valtech-components] AuthService: provideValtechAuth() no está en main.ts. " +
|
|
25811
|
+
"Login, registro y sesión no funcionarán.");
|
|
25812
|
+
}
|
|
25803
25813
|
}
|
|
25804
25814
|
// =============================================
|
|
25805
25815
|
// INICIALIZACIÓN
|
|
@@ -27868,6 +27878,7 @@ class ImageCropComponent {
|
|
|
27868
27878
|
color="primary"
|
|
27869
27879
|
[strong]="true"
|
|
27870
27880
|
[disabled]="!croppedBlob()"
|
|
27881
|
+
data-testid="image-crop-confirm"
|
|
27871
27882
|
>
|
|
27872
27883
|
{{ confirmText() }}
|
|
27873
27884
|
</ion-button>
|
|
@@ -27892,16 +27903,7 @@ class ImageCropComponent {
|
|
|
27892
27903
|
}
|
|
27893
27904
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ImageCropComponent, decorators: [{
|
|
27894
27905
|
type: Component,
|
|
27895
|
-
args: [{ selector: 'val-image-crop', standalone: true, imports: [
|
|
27896
|
-
CommonModule,
|
|
27897
|
-
IonHeader,
|
|
27898
|
-
IonToolbar,
|
|
27899
|
-
IonTitle,
|
|
27900
|
-
IonButtons,
|
|
27901
|
-
IonButton,
|
|
27902
|
-
IonContent,
|
|
27903
|
-
ImageCropperComponent,
|
|
27904
|
-
], template: `
|
|
27906
|
+
args: [{ selector: 'val-image-crop', standalone: true, imports: [CommonModule, IonHeader, IonToolbar, IonTitle, IonButtons, IonButton, IonContent, ImageCropperComponent], template: `
|
|
27905
27907
|
<ion-header>
|
|
27906
27908
|
<ion-toolbar>
|
|
27907
27909
|
<ion-buttons slot="start">
|
|
@@ -27916,6 +27918,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
27916
27918
|
color="primary"
|
|
27917
27919
|
[strong]="true"
|
|
27918
27920
|
[disabled]="!croppedBlob()"
|
|
27921
|
+
data-testid="image-crop-confirm"
|
|
27919
27922
|
>
|
|
27920
27923
|
{{ confirmText() }}
|
|
27921
27924
|
</ion-button>
|
|
@@ -28123,17 +28126,22 @@ function provideValtechFeedback(config) {
|
|
|
28123
28126
|
*/
|
|
28124
28127
|
class FeedbackService {
|
|
28125
28128
|
constructor() {
|
|
28126
|
-
this.config = inject(VALTECH_FEEDBACK_CONFIG);
|
|
28129
|
+
this.config = inject(VALTECH_FEEDBACK_CONFIG, { optional: true });
|
|
28127
28130
|
this.http = inject(HttpClient);
|
|
28128
28131
|
this.firestore = inject(FirestoreService, { optional: true });
|
|
28129
28132
|
this.storage = inject(StorageService, { optional: true });
|
|
28130
28133
|
this.auth = inject(AuthService, { optional: true });
|
|
28134
|
+
this.isConfigured = signal(!!this.config);
|
|
28135
|
+
if (!this.config && isDevMode()) {
|
|
28136
|
+
console.warn('[valtech-components] FeedbackService: provideValtechFeedback() no está en main.ts. ' +
|
|
28137
|
+
'Las reacciones y el feedback no funcionarán.');
|
|
28138
|
+
}
|
|
28131
28139
|
}
|
|
28132
28140
|
/**
|
|
28133
28141
|
* URL base para endpoints de feedback.
|
|
28134
28142
|
*/
|
|
28135
28143
|
get baseUrl() {
|
|
28136
|
-
return `${this.config
|
|
28144
|
+
return `${this.config?.apiUrl}${this.config?.feedbackPrefix}`;
|
|
28137
28145
|
}
|
|
28138
28146
|
/**
|
|
28139
28147
|
* Captura el contexto del dispositivo automáticamente.
|
|
@@ -28167,7 +28175,7 @@ class FeedbackService {
|
|
|
28167
28175
|
attachments,
|
|
28168
28176
|
contentRef,
|
|
28169
28177
|
deviceContext: this.captureDeviceContext(),
|
|
28170
|
-
appId: this.config
|
|
28178
|
+
appId: this.config?.appId,
|
|
28171
28179
|
};
|
|
28172
28180
|
return this.http.post(this.baseUrl, request);
|
|
28173
28181
|
}
|
|
@@ -28197,15 +28205,15 @@ class FeedbackService {
|
|
|
28197
28205
|
*/
|
|
28198
28206
|
validateFile(file) {
|
|
28199
28207
|
// Verificar tamaño
|
|
28200
|
-
if (file.size > this.config
|
|
28201
|
-
const maxSizeMB = Math.round(this.config
|
|
28208
|
+
if (file.size > this.config?.maxFileSize) {
|
|
28209
|
+
const maxSizeMB = Math.round(this.config?.maxFileSize / (1024 * 1024));
|
|
28202
28210
|
return {
|
|
28203
28211
|
valid: false,
|
|
28204
28212
|
error: `El archivo excede el tamaño máximo de ${maxSizeMB}MB`,
|
|
28205
28213
|
};
|
|
28206
28214
|
}
|
|
28207
28215
|
// Verificar tipo
|
|
28208
|
-
const allowedTypes = this.config
|
|
28216
|
+
const allowedTypes = this.config?.allowedFileTypes || [];
|
|
28209
28217
|
const isAllowed = allowedTypes.some(pattern => {
|
|
28210
28218
|
if (pattern.endsWith('/*')) {
|
|
28211
28219
|
const baseType = pattern.replace('/*', '');
|
|
@@ -28367,7 +28375,7 @@ class FeedbackService {
|
|
|
28367
28375
|
}
|
|
28368
28376
|
// 2. Fallback: llamar API (solo si hay usuario autenticado)
|
|
28369
28377
|
const params = new URLSearchParams({
|
|
28370
|
-
appId: this.config
|
|
28378
|
+
appId: this.config?.appId,
|
|
28371
28379
|
entityType,
|
|
28372
28380
|
entityId,
|
|
28373
28381
|
});
|
|
@@ -28397,7 +28405,7 @@ class FeedbackService {
|
|
|
28397
28405
|
reactionValue: value,
|
|
28398
28406
|
description: comment || '',
|
|
28399
28407
|
deviceContext: this.captureDeviceContext(),
|
|
28400
|
-
appId: this.config
|
|
28408
|
+
appId: this.config?.appId,
|
|
28401
28409
|
};
|
|
28402
28410
|
return firstValueFrom(this.http.post(this.baseUrl, request));
|
|
28403
28411
|
}
|
|
@@ -28426,7 +28434,7 @@ class FeedbackService {
|
|
|
28426
28434
|
reactionValue: value,
|
|
28427
28435
|
description: comment || '',
|
|
28428
28436
|
deviceContext: this.captureDeviceContext(),
|
|
28429
|
-
appId: this.config
|
|
28437
|
+
appId: this.config?.appId,
|
|
28430
28438
|
};
|
|
28431
28439
|
return firstValueFrom(this.http.post(`${this.baseUrl}/anonymous`, request));
|
|
28432
28440
|
}
|
|
@@ -28474,7 +28482,7 @@ class FeedbackService {
|
|
|
28474
28482
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FeedbackService, decorators: [{
|
|
28475
28483
|
type: Injectable,
|
|
28476
28484
|
args: [{ providedIn: 'root' }]
|
|
28477
|
-
}] });
|
|
28485
|
+
}], ctorParameters: () => [] });
|
|
28478
28486
|
|
|
28479
28487
|
/**
|
|
28480
28488
|
* Configuración por defecto de tipos de feedback.
|
|
@@ -38909,7 +38917,14 @@ class AvatarUploadComponent {
|
|
|
38909
38917
|
</div>
|
|
38910
38918
|
|
|
38911
38919
|
<!-- Hidden File Input -->
|
|
38912
|
-
<input
|
|
38920
|
+
<input
|
|
38921
|
+
#fileInput
|
|
38922
|
+
type="file"
|
|
38923
|
+
accept="image/jpeg,image/png,image/webp"
|
|
38924
|
+
(change)="onFileSelected($event)"
|
|
38925
|
+
data-testid="avatar-upload-input"
|
|
38926
|
+
hidden
|
|
38927
|
+
/>
|
|
38913
38928
|
|
|
38914
38929
|
<!-- Crop Modal -->
|
|
38915
38930
|
<ion-modal
|
|
@@ -38965,7 +38980,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
38965
38980
|
</div>
|
|
38966
38981
|
|
|
38967
38982
|
<!-- Hidden File Input -->
|
|
38968
|
-
<input
|
|
38983
|
+
<input
|
|
38984
|
+
#fileInput
|
|
38985
|
+
type="file"
|
|
38986
|
+
accept="image/jpeg,image/png,image/webp"
|
|
38987
|
+
(change)="onFileSelected($event)"
|
|
38988
|
+
data-testid="avatar-upload-input"
|
|
38989
|
+
hidden
|
|
38990
|
+
/>
|
|
38969
38991
|
|
|
38970
38992
|
<!-- Crop Modal -->
|
|
38971
38993
|
<ion-modal
|
|
@@ -41912,6 +41934,15 @@ class OrgService {
|
|
|
41912
41934
|
.post(`${this.baseUrl}/${orgId}/transfer-ownership`, { newOwnerId })
|
|
41913
41935
|
.pipe(map$1(r => r.organization));
|
|
41914
41936
|
}
|
|
41937
|
+
validateInviteToken(token) {
|
|
41938
|
+
return this.http.post(`${this.config.apiUrl}/v2/actions/validate`, { token });
|
|
41939
|
+
}
|
|
41940
|
+
executeInviteToken(token) {
|
|
41941
|
+
return this.http.post(`${this.config.apiUrl}/v2/actions/execute`, { token });
|
|
41942
|
+
}
|
|
41943
|
+
getPendingInvitations() {
|
|
41944
|
+
return this.http.get(`${this.config.apiUrl}/organizations/invitations/pending`);
|
|
41945
|
+
}
|
|
41915
41946
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OrgService, deps: [{ token: VALTECH_AUTH_CONFIG }, { token: i1$8.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
41916
41947
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OrgService, providedIn: 'root' }); }
|
|
41917
41948
|
}
|
|
@@ -46072,6 +46103,7 @@ class ContentReactionComponent {
|
|
|
46072
46103
|
this.toast = inject(ToastService);
|
|
46073
46104
|
this.i18n = inject(I18nService);
|
|
46074
46105
|
this.auth = inject(AuthService, { optional: true });
|
|
46106
|
+
this.isConfigured = computed(() => this.feedbackService.isConfigured());
|
|
46075
46107
|
this.props = {};
|
|
46076
46108
|
this.reactionSubmit = new EventEmitter();
|
|
46077
46109
|
this.reactionChange = new EventEmitter();
|
|
@@ -46249,11 +46281,11 @@ class ContentReactionComponent {
|
|
|
46249
46281
|
return this.i18n.t(key, 'ContentReaction') || translations[key] || key;
|
|
46250
46282
|
}
|
|
46251
46283
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ContentReactionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
46252
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ContentReactionComponent, isStandalone: true, selector: "val-content-reaction", inputs: { props: "props" }, outputs: { reactionSubmit: "reactionSubmit", reactionChange: "reactionChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"content-reaction\" [class.disabled]=\"resolvedProps().disabled\" [class.readonly]=\"resolvedProps().readonly\">\n <!-- Loading inicial -->\n @if (state().isLoading && !state().selectedValue) {\n <div class=\"loading-container\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @else {\n <!-- Pregunta -->\n <p class=\"question\">{{ resolvedProps().question }}</p>\n\n <!-- Emojis -->\n <div class=\"emoji-container\">\n @for (value of activeReactionValues(); track value; let i = $index) {\n <button\n type=\"button\"\n class=\"emoji-button\"\n [class.selected]=\"isSelected(value)\"\n [class.negative]=\"value === 'negative' && isSelected(value)\"\n [class.neutral]=\"value === 'neutral' && isSelected(value)\"\n [class.positive]=\"value === 'positive' && isSelected(value)\"\n [attr.aria-label]=\"getEmojiLabel(i)\"\n [attr.aria-pressed]=\"isSelected(value)\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction(value)\"\n >\n <span class=\"emoji\">{{ getEmoji(i) }}</span>\n </button>\n }\n </div>\n\n <!-- Campo de comentario (solo si hay selecci\u00F3n) -->\n @if (showCommentField()) {\n <div class=\"comment-section\">\n <ion-textarea\n [value]=\"state().comment\"\n [placeholder]=\"resolvedProps().commentPlaceholder\"\n [maxlength]=\"resolvedProps().maxCommentLength\"\n [disabled]=\"resolvedProps().disabled\"\n [rows]=\"3\"\n class=\"comment-textarea\"\n (ionInput)=\"updateComment($event)\"\n ></ion-textarea>\n <span class=\"char-count\"> {{ state().comment.length }}/{{ resolvedProps().maxCommentLength }} </span>\n </div>\n }\n\n <!-- Bot\u00F3n de env\u00EDo -->\n @if (state().selectedValue && !state().isSubmitted) {\n <ion-button expand=\"block\" [disabled]=\"!canSubmit()\" (click)=\"submitReaction()\" class=\"submit-button\">\n @if (state().isLoading) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ state().hadPreviousReaction ? t('update') : t('submit') }} }\n </ion-button>\n }\n\n <!-- Mensaje de confirmaci\u00F3n -->\n @if (state().isSubmitted) {\n <p class=\"submitted-message\">{{ t('thankYou') }}</p>\n }\n\n <!-- Error -->\n @if (state().error) {\n <p class=\"error-message\">{{ state().error }}</p>\n } }\n</div>\n", styles: [":host{display:block}.content-reaction{padding:16px;text-align:center}.content-reaction.disabled{opacity:.6;pointer-events:none}.content-reaction.readonly{pointer-events:none}.question{font-size:16px;font-weight:500;color:var(--ion-color-dark);margin:0 0 16px}.emoji-container{display:flex;justify-content:center;gap:24px;margin-bottom:20px}.emoji-button{background:transparent;border:2px solid var(--ion-color-light-shade);border-radius:50%;width:64px;height:64px;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);display:flex;align-items:center;justify-content:center;padding:0}.emoji-button .emoji{font-size:32px;transition:transform .3s cubic-bezier(.4,0,.2,1);line-height:1}.emoji-button:hover:not(:disabled){transform:scale(1.1);border-color:var(--ion-color-medium)}.emoji-button:focus{outline:2px solid var(--ion-color-primary);outline-offset:2px}.emoji-button.selected{transform:scale(1.2)}.emoji-button.selected .emoji{transform:scale(1.1)}.emoji-button.selected.negative{border-color:var(--ion-color-danger);background:var(--ion-color-danger-tint);box-shadow:0 4px 12px rgba(var(--ion-color-danger-rgb),.3)}.emoji-button.selected.neutral{border-color:var(--ion-color-warning);background:var(--ion-color-warning-tint);box-shadow:0 4px 12px rgba(var(--ion-color-warning-rgb),.3)}.emoji-button.selected.positive{border-color:var(--ion-color-success);background:var(--ion-color-success-tint);box-shadow:0 4px 12px rgba(var(--ion-color-success-rgb),.3)}.emoji-button:disabled{cursor:not-allowed;opacity:.5}.comment-section{margin-top:16px;animation:slideIn .3s ease-out}.comment-section .comment-textarea{--background: var(--ion-color-light);--border-radius: 8px;--padding-start: 12px;--padding-end: 12px;width:100%}.comment-section .char-count{display:block;text-align:right;font-size:12px;color:var(--ion-color-medium);margin-top:4px}.submit-button{margin-top:16px;--border-radius: 8px}.submitted-message{margin-top:16px;color:var(--ion-color-success);font-weight:500;animation:fadeIn .3s ease-out}.error-message{margin-top:8px;color:var(--ion-color-danger);font-size:14px}.loading-container{display:flex;justify-content:center;padding:20px}@keyframes slideIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }] }); }
|
|
46284
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ContentReactionComponent, isStandalone: true, selector: "val-content-reaction", inputs: { props: "props" }, outputs: { reactionSubmit: "reactionSubmit", reactionChange: "reactionChange" }, usesOnChanges: true, ngImport: i0, template: "@if (isConfigured()) {\n<div class=\"content-reaction\" [class.disabled]=\"resolvedProps().disabled\" [class.readonly]=\"resolvedProps().readonly\">\n <!-- Loading inicial -->\n @if (state().isLoading && !state().selectedValue) {\n <div class=\"loading-container\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @else {\n <!-- Pregunta -->\n <p class=\"question\">{{ resolvedProps().question }}</p>\n\n <!-- Emojis -->\n <div class=\"emoji-container\">\n @for (value of activeReactionValues(); track value; let i = $index) {\n <button\n type=\"button\"\n class=\"emoji-button\"\n [class.selected]=\"isSelected(value)\"\n [class.negative]=\"value === 'negative' && isSelected(value)\"\n [class.neutral]=\"value === 'neutral' && isSelected(value)\"\n [class.positive]=\"value === 'positive' && isSelected(value)\"\n [attr.aria-label]=\"getEmojiLabel(i)\"\n [attr.aria-pressed]=\"isSelected(value)\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction(value)\"\n >\n <span class=\"emoji\">{{ getEmoji(i) }}</span>\n </button>\n }\n </div>\n\n <!-- Campo de comentario (solo si hay selecci\u00F3n) -->\n @if (showCommentField()) {\n <div class=\"comment-section\">\n <ion-textarea\n [value]=\"state().comment\"\n [placeholder]=\"resolvedProps().commentPlaceholder\"\n [maxlength]=\"resolvedProps().maxCommentLength\"\n [disabled]=\"resolvedProps().disabled\"\n [rows]=\"3\"\n class=\"comment-textarea\"\n (ionInput)=\"updateComment($event)\"\n ></ion-textarea>\n <span class=\"char-count\"> {{ state().comment.length }}/{{ resolvedProps().maxCommentLength }} </span>\n </div>\n }\n\n <!-- Bot\u00F3n de env\u00EDo -->\n @if (state().selectedValue && !state().isSubmitted) {\n <ion-button expand=\"block\" [disabled]=\"!canSubmit()\" (click)=\"submitReaction()\" class=\"submit-button\">\n @if (state().isLoading) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ state().hadPreviousReaction ? t('update') : t('submit') }} }\n </ion-button>\n }\n\n <!-- Mensaje de confirmaci\u00F3n -->\n @if (state().isSubmitted) {\n <p class=\"submitted-message\">{{ t('thankYou') }}</p>\n }\n\n <!-- Error -->\n @if (state().error) {\n <p class=\"error-message\">{{ state().error }}</p>\n } }\n</div>\n}\n", styles: [":host{display:block}.content-reaction{padding:16px;text-align:center}.content-reaction.disabled{opacity:.6;pointer-events:none}.content-reaction.readonly{pointer-events:none}.question{font-size:16px;font-weight:500;color:var(--ion-color-dark);margin:0 0 16px}.emoji-container{display:flex;justify-content:center;gap:24px;margin-bottom:20px}.emoji-button{background:transparent;border:2px solid var(--ion-color-light-shade);border-radius:50%;width:64px;height:64px;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);display:flex;align-items:center;justify-content:center;padding:0}.emoji-button .emoji{font-size:32px;transition:transform .3s cubic-bezier(.4,0,.2,1);line-height:1}.emoji-button:hover:not(:disabled){transform:scale(1.1);border-color:var(--ion-color-medium)}.emoji-button:focus{outline:2px solid var(--ion-color-primary);outline-offset:2px}.emoji-button.selected{transform:scale(1.2)}.emoji-button.selected .emoji{transform:scale(1.1)}.emoji-button.selected.negative{border-color:var(--ion-color-danger);background:var(--ion-color-danger-tint);box-shadow:0 4px 12px rgba(var(--ion-color-danger-rgb),.3)}.emoji-button.selected.neutral{border-color:var(--ion-color-warning);background:var(--ion-color-warning-tint);box-shadow:0 4px 12px rgba(var(--ion-color-warning-rgb),.3)}.emoji-button.selected.positive{border-color:var(--ion-color-success);background:var(--ion-color-success-tint);box-shadow:0 4px 12px rgba(var(--ion-color-success-rgb),.3)}.emoji-button:disabled{cursor:not-allowed;opacity:.5}.comment-section{margin-top:16px;animation:slideIn .3s ease-out}.comment-section .comment-textarea{--background: var(--ion-color-light);--border-radius: 8px;--padding-start: 12px;--padding-end: 12px;width:100%}.comment-section .char-count{display:block;text-align:right;font-size:12px;color:var(--ion-color-medium);margin-top:4px}.submit-button{margin-top:16px;--border-radius: 8px}.submitted-message{margin-top:16px;color:var(--ion-color-success);font-weight:500;animation:fadeIn .3s ease-out}.error-message{margin-top:8px;color:var(--ion-color-danger);font-size:14px}.loading-container{display:flex;justify-content:center;padding:20px}@keyframes slideIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }] }); }
|
|
46253
46285
|
}
|
|
46254
46286
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ContentReactionComponent, decorators: [{
|
|
46255
46287
|
type: Component,
|
|
46256
|
-
args: [{ selector: 'val-content-reaction', standalone: true, imports: [CommonModule, FormsModule, IonButton, IonSpinner, IonTextarea], template: "<div class=\"content-reaction\" [class.disabled]=\"resolvedProps().disabled\" [class.readonly]=\"resolvedProps().readonly\">\n <!-- Loading inicial -->\n @if (state().isLoading && !state().selectedValue) {\n <div class=\"loading-container\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @else {\n <!-- Pregunta -->\n <p class=\"question\">{{ resolvedProps().question }}</p>\n\n <!-- Emojis -->\n <div class=\"emoji-container\">\n @for (value of activeReactionValues(); track value; let i = $index) {\n <button\n type=\"button\"\n class=\"emoji-button\"\n [class.selected]=\"isSelected(value)\"\n [class.negative]=\"value === 'negative' && isSelected(value)\"\n [class.neutral]=\"value === 'neutral' && isSelected(value)\"\n [class.positive]=\"value === 'positive' && isSelected(value)\"\n [attr.aria-label]=\"getEmojiLabel(i)\"\n [attr.aria-pressed]=\"isSelected(value)\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction(value)\"\n >\n <span class=\"emoji\">{{ getEmoji(i) }}</span>\n </button>\n }\n </div>\n\n <!-- Campo de comentario (solo si hay selecci\u00F3n) -->\n @if (showCommentField()) {\n <div class=\"comment-section\">\n <ion-textarea\n [value]=\"state().comment\"\n [placeholder]=\"resolvedProps().commentPlaceholder\"\n [maxlength]=\"resolvedProps().maxCommentLength\"\n [disabled]=\"resolvedProps().disabled\"\n [rows]=\"3\"\n class=\"comment-textarea\"\n (ionInput)=\"updateComment($event)\"\n ></ion-textarea>\n <span class=\"char-count\"> {{ state().comment.length }}/{{ resolvedProps().maxCommentLength }} </span>\n </div>\n }\n\n <!-- Bot\u00F3n de env\u00EDo -->\n @if (state().selectedValue && !state().isSubmitted) {\n <ion-button expand=\"block\" [disabled]=\"!canSubmit()\" (click)=\"submitReaction()\" class=\"submit-button\">\n @if (state().isLoading) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ state().hadPreviousReaction ? t('update') : t('submit') }} }\n </ion-button>\n }\n\n <!-- Mensaje de confirmaci\u00F3n -->\n @if (state().isSubmitted) {\n <p class=\"submitted-message\">{{ t('thankYou') }}</p>\n }\n\n <!-- Error -->\n @if (state().error) {\n <p class=\"error-message\">{{ state().error }}</p>\n } }\n</div>\n", styles: [":host{display:block}.content-reaction{padding:16px;text-align:center}.content-reaction.disabled{opacity:.6;pointer-events:none}.content-reaction.readonly{pointer-events:none}.question{font-size:16px;font-weight:500;color:var(--ion-color-dark);margin:0 0 16px}.emoji-container{display:flex;justify-content:center;gap:24px;margin-bottom:20px}.emoji-button{background:transparent;border:2px solid var(--ion-color-light-shade);border-radius:50%;width:64px;height:64px;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);display:flex;align-items:center;justify-content:center;padding:0}.emoji-button .emoji{font-size:32px;transition:transform .3s cubic-bezier(.4,0,.2,1);line-height:1}.emoji-button:hover:not(:disabled){transform:scale(1.1);border-color:var(--ion-color-medium)}.emoji-button:focus{outline:2px solid var(--ion-color-primary);outline-offset:2px}.emoji-button.selected{transform:scale(1.2)}.emoji-button.selected .emoji{transform:scale(1.1)}.emoji-button.selected.negative{border-color:var(--ion-color-danger);background:var(--ion-color-danger-tint);box-shadow:0 4px 12px rgba(var(--ion-color-danger-rgb),.3)}.emoji-button.selected.neutral{border-color:var(--ion-color-warning);background:var(--ion-color-warning-tint);box-shadow:0 4px 12px rgba(var(--ion-color-warning-rgb),.3)}.emoji-button.selected.positive{border-color:var(--ion-color-success);background:var(--ion-color-success-tint);box-shadow:0 4px 12px rgba(var(--ion-color-success-rgb),.3)}.emoji-button:disabled{cursor:not-allowed;opacity:.5}.comment-section{margin-top:16px;animation:slideIn .3s ease-out}.comment-section .comment-textarea{--background: var(--ion-color-light);--border-radius: 8px;--padding-start: 12px;--padding-end: 12px;width:100%}.comment-section .char-count{display:block;text-align:right;font-size:12px;color:var(--ion-color-medium);margin-top:4px}.submit-button{margin-top:16px;--border-radius: 8px}.submitted-message{margin-top:16px;color:var(--ion-color-success);font-weight:500;animation:fadeIn .3s ease-out}.error-message{margin-top:8px;color:var(--ion-color-danger);font-size:14px}.loading-container{display:flex;justify-content:center;padding:20px}@keyframes slideIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"] }]
|
|
46288
|
+
args: [{ selector: 'val-content-reaction', standalone: true, imports: [CommonModule, FormsModule, IonButton, IonSpinner, IonTextarea], template: "@if (isConfigured()) {\n<div class=\"content-reaction\" [class.disabled]=\"resolvedProps().disabled\" [class.readonly]=\"resolvedProps().readonly\">\n <!-- Loading inicial -->\n @if (state().isLoading && !state().selectedValue) {\n <div class=\"loading-container\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n </div>\n } @else {\n <!-- Pregunta -->\n <p class=\"question\">{{ resolvedProps().question }}</p>\n\n <!-- Emojis -->\n <div class=\"emoji-container\">\n @for (value of activeReactionValues(); track value; let i = $index) {\n <button\n type=\"button\"\n class=\"emoji-button\"\n [class.selected]=\"isSelected(value)\"\n [class.negative]=\"value === 'negative' && isSelected(value)\"\n [class.neutral]=\"value === 'neutral' && isSelected(value)\"\n [class.positive]=\"value === 'positive' && isSelected(value)\"\n [attr.aria-label]=\"getEmojiLabel(i)\"\n [attr.aria-pressed]=\"isSelected(value)\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction(value)\"\n >\n <span class=\"emoji\">{{ getEmoji(i) }}</span>\n </button>\n }\n </div>\n\n <!-- Campo de comentario (solo si hay selecci\u00F3n) -->\n @if (showCommentField()) {\n <div class=\"comment-section\">\n <ion-textarea\n [value]=\"state().comment\"\n [placeholder]=\"resolvedProps().commentPlaceholder\"\n [maxlength]=\"resolvedProps().maxCommentLength\"\n [disabled]=\"resolvedProps().disabled\"\n [rows]=\"3\"\n class=\"comment-textarea\"\n (ionInput)=\"updateComment($event)\"\n ></ion-textarea>\n <span class=\"char-count\"> {{ state().comment.length }}/{{ resolvedProps().maxCommentLength }} </span>\n </div>\n }\n\n <!-- Bot\u00F3n de env\u00EDo -->\n @if (state().selectedValue && !state().isSubmitted) {\n <ion-button expand=\"block\" [disabled]=\"!canSubmit()\" (click)=\"submitReaction()\" class=\"submit-button\">\n @if (state().isLoading) {\n <ion-spinner name=\"crescent\"></ion-spinner>\n } @else { {{ state().hadPreviousReaction ? t('update') : t('submit') }} }\n </ion-button>\n }\n\n <!-- Mensaje de confirmaci\u00F3n -->\n @if (state().isSubmitted) {\n <p class=\"submitted-message\">{{ t('thankYou') }}</p>\n }\n\n <!-- Error -->\n @if (state().error) {\n <p class=\"error-message\">{{ state().error }}</p>\n } }\n</div>\n}\n", styles: [":host{display:block}.content-reaction{padding:16px;text-align:center}.content-reaction.disabled{opacity:.6;pointer-events:none}.content-reaction.readonly{pointer-events:none}.question{font-size:16px;font-weight:500;color:var(--ion-color-dark);margin:0 0 16px}.emoji-container{display:flex;justify-content:center;gap:24px;margin-bottom:20px}.emoji-button{background:transparent;border:2px solid var(--ion-color-light-shade);border-radius:50%;width:64px;height:64px;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);display:flex;align-items:center;justify-content:center;padding:0}.emoji-button .emoji{font-size:32px;transition:transform .3s cubic-bezier(.4,0,.2,1);line-height:1}.emoji-button:hover:not(:disabled){transform:scale(1.1);border-color:var(--ion-color-medium)}.emoji-button:focus{outline:2px solid var(--ion-color-primary);outline-offset:2px}.emoji-button.selected{transform:scale(1.2)}.emoji-button.selected .emoji{transform:scale(1.1)}.emoji-button.selected.negative{border-color:var(--ion-color-danger);background:var(--ion-color-danger-tint);box-shadow:0 4px 12px rgba(var(--ion-color-danger-rgb),.3)}.emoji-button.selected.neutral{border-color:var(--ion-color-warning);background:var(--ion-color-warning-tint);box-shadow:0 4px 12px rgba(var(--ion-color-warning-rgb),.3)}.emoji-button.selected.positive{border-color:var(--ion-color-success);background:var(--ion-color-success-tint);box-shadow:0 4px 12px rgba(var(--ion-color-success-rgb),.3)}.emoji-button:disabled{cursor:not-allowed;opacity:.5}.comment-section{margin-top:16px;animation:slideIn .3s ease-out}.comment-section .comment-textarea{--background: var(--ion-color-light);--border-radius: 8px;--padding-start: 12px;--padding-end: 12px;width:100%}.comment-section .char-count{display:block;text-align:right;font-size:12px;color:var(--ion-color-medium);margin-top:4px}.submit-button{margin-top:16px;--border-radius: 8px}.submitted-message{margin-top:16px;color:var(--ion-color-success);font-weight:500;animation:fadeIn .3s ease-out}.error-message{margin-top:8px;color:var(--ion-color-danger);font-size:14px}.loading-container{display:flex;justify-content:center;padding:20px}@keyframes slideIn{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}\n"] }]
|
|
46257
46289
|
}], propDecorators: { props: [{
|
|
46258
46290
|
type: Input
|
|
46259
46291
|
}], reactionSubmit: [{
|