valtech-components 2.0.1004 → 2.0.1005

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.
@@ -25,7 +25,7 @@ import { getApps, getApp, initializeApp as initializeApp$1 } from 'firebase/app'
25
25
  import * as i2$1 from '@ionic/angular/standalone';
26
26
  import { AlertController, 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, IonInput, IonSelect, IonSelectOption, IonPopover, IonList, IonItem, IonRadioGroup, IonRadio, IonRange, IonSearchbar, IonSegment, IonSegmentButton, IonToggle, IonRippleEffect, IonAccordion, IonAccordionGroup, IonTabBar, IonTabButton, IonBadge, IonBreadcrumb, IonBreadcrumbs, IonChip, IonNote, IonDatetimeButton, ToastController as ToastController$1, IonCol, IonRow, IonRefresher, IonRefresherContent, IonMenuButton, IonFooter, IonListHeader, IonInfiniteScroll, IonInfiniteScrollContent, IonGrid, MenuController, IonMenu, IonMenuToggle, IonSplitPane } from '@ionic/angular/standalone';
27
27
  import { addIcons } from 'ionicons';
28
- 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, peopleOutline, phonePortraitOutline, refreshOutline, documentTextOutline, lockClosedOutline, informationCircleOutline, logoNpm, removeOutline, optionsOutline, personOutline, shieldCheckmarkOutline, keyOutline, desktopOutline, logOutOutline, cloudDownloadOutline, cloudOutline, gitBranchOutline, layersOutline, serverOutline, warningOutline, bulbOutline, lockOpenOutline, diamondOutline, 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, logoWhatsapp, paperPlaneOutline, mailOutline, chevronDownCircleOutline, closeCircle, alertCircle, logoApple, logoMicrosoft, linkOutline, unlinkOutline, cloudOfflineOutline, documentOutline, attachOutline, cameraOutline, closeCircleOutline, imageOutline, chevronBackOutline, sendOutline, chatbubbleEllipsesOutline, swapVerticalOutline, chevronUpOutline, searchOutline, cartOutline, chatbubble, compass, compassOutline, gridOutline, listOutline, folderOutline, documents, documentsOutline, statsChart, statsChartOutline, laptopOutline, swapHorizontalOutline, notificationsOffOutline, alarmOutline, chatboxEllipsesOutline, checkmarkDoneOutline, cogOutline, menuOutline } from 'ionicons/icons';
28
+ 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, peopleOutline, phonePortraitOutline, refreshOutline, documentTextOutline, lockClosedOutline, informationCircleOutline, logoNpm, removeOutline, optionsOutline, personOutline, shieldCheckmarkOutline, keyOutline, desktopOutline, logOutOutline, cloudDownloadOutline, cloudOutline, gitBranchOutline, layersOutline, serverOutline, warningOutline, bulbOutline, lockOpenOutline, diamondOutline, 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, logoWhatsapp, paperPlaneOutline, mailOutline, chevronDownCircleOutline, closeCircle, alertCircle, logoApple, logoMicrosoft, linkOutline, unlinkOutline, cloudOfflineOutline, documentOutline, attachOutline, cameraOutline, closeCircleOutline, imageOutline, chevronBackOutline, sendOutline, chatbubbleEllipsesOutline, swapVerticalOutline, chevronUpOutline, searchOutline, cartOutline, chatbubble, compass, compassOutline, gridOutline, listOutline, folderOutline, documents, documentsOutline, statsChart, statsChartOutline, laptopOutline, swapHorizontalOutline, notificationsOffOutline, alarmOutline, chatboxEllipsesOutline, checkmarkDoneOutline, cogOutline, shareSocialOutline, helpCircleOutline, menuOutline } from 'ionicons/icons';
29
29
  import { Browser } from '@capacitor/browser';
30
30
  import * as i1$6 from '@angular/platform-browser';
31
31
  import { DomSanitizer, Meta, Title } from '@angular/platform-browser';
@@ -54,7 +54,7 @@ import 'prismjs/components/prism-json';
54
54
  * Current version of valtech-components.
55
55
  * This is automatically updated during the publish process.
56
56
  */
57
- const VERSION = '2.0.1004';
57
+ const VERSION = '2.0.1005';
58
58
 
59
59
  // Control de estado de refresco (singleton a nivel de módulo)
60
60
  let isRefreshing = false;
@@ -21657,7 +21657,7 @@ class CommentComponent {
21657
21657
  </div>
21658
21658
  }
21659
21659
  </div>
21660
- `, isInline: true, styles: ["@charset \"UTF-8\";:root{--val-container-sm: 540px;--val-container-md: 720px;--val-container-lg: 880px;--val-container-xl: 1100px;--val-container-padding: 16px;--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}body.dark,html.ion-palette-dark,body[data-theme=dark]{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}:host{display:block}.comment{--indent-size: 24px;--max-indent: 72px;padding:12px 0}.comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.08);border-radius:8px;padding:12px;margin:-4px}.comment.pinned{border-left:3px solid var(--ion-color-primary);padding-left:12px}.comment.collapsed{opacity:.7}.pinned-indicator{display:flex;align-items:center;gap:4px;font-size:.75rem;color:var(--ion-color-primary);margin-bottom:8px;padding-left:44px}.pinned-indicator ion-icon{font-size:.875rem}.comment-main{display:flex;gap:12px}.comment-avatar{flex-shrink:0;cursor:pointer}.comment-avatar val-avatar{--size: 36px}.comment-avatar .avatar-placeholder{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--ion-color-primary),var(--ion-color-secondary));display:flex;align-items:center;justify-content:center;color:#fff;font-size:.875rem;font-weight:600}.comment-body{flex:1;min-width:0}.comment-header{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin-bottom:4px}.author-info{display:flex;align-items:center;gap:4px;cursor:pointer}.author-info:hover .author-name{text-decoration:underline}.author-name{font-weight:600;font-size:.875rem;color:var(--ion-text-color)}.verified-badge{color:var(--ion-color-primary);font-size:1rem}.author-username{font-size:.8125rem;color:var(--ion-color-medium)}.comment-meta{display:flex;align-items:center;gap:6px;margin-left:auto}.timestamp{font-size:.75rem;color:var(--ion-color-medium)}.edited-label{font-size:.6875rem;color:var(--ion-color-medium);font-style:italic}.more-button{--padding-start: 4px;--padding-end: 4px;margin:0;height:28px}.more-button ion-icon{font-size:1.125rem;color:var(--ion-color-medium)}.menu-divider{height:1px;background-color:var(--ion-color-light);margin:4px 0}.comment-content{font-size:.875rem;line-height:1.5;color:var(--ion-text-color);word-wrap:break-word;overflow-wrap:break-word}.comment-content :global(.comment-link){color:var(--ion-color-primary);text-decoration:none}.comment-content :global(.comment-link):hover{text-decoration:underline}.comment-content :global(.comment-mention){color:var(--ion-color-primary);font-weight:500;cursor:pointer}.comment-content :global(.comment-mention):hover{text-decoration:underline}.comment-content :global(.comment-hashtag){color:var(--ion-color-primary);cursor:pointer}.comment-content :global(.comment-hashtag):hover{text-decoration:underline}.comment-reactions{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}.reaction-button{display:flex;align-items:center;gap:4px;background:none;border:none;padding:4px 8px;border-radius:16px;cursor:pointer;font-size:.8125rem;color:var(--reaction-color, var(--ion-color-medium));transition:all .2s ease;background-color:transparent}.reaction-button:hover{background-color:rgba(var(--ion-color-medium-rgb),.1)}.reaction-button.active{background-color:rgba(var(--ion-color-primary-rgb),.1)}.reaction-button.active ion-icon{transform:scale(1.1)}.reaction-button ion-icon{font-size:1.125rem;transition:transform .2s ease}.reaction-button .reaction-count{font-weight:500}.comment-actions{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;margin-left:-8px}.comment-actions ion-button{--padding-start: 8px;--padding-end: 8px;font-size:.8125rem;height:32px;text-transform:none;font-weight:500}.expand-button,.collapse-button,.load-more-button,.view-replies-button{display:flex;align-items:center;gap:6px;background:none;border:none;padding:8px 12px;cursor:pointer;font-size:.8125rem;color:var(--ion-color-primary);border-radius:8px;transition:background-color .2s}.expand-button:hover,.collapse-button:hover,.load-more-button:hover,.view-replies-button:hover{background-color:rgba(var(--ion-color-primary-rgb),.1)}.expand-button ion-icon,.collapse-button ion-icon,.load-more-button ion-icon,.view-replies-button ion-icon{font-size:1rem}.expand-button{margin-top:4px;color:var(--ion-color-medium)}.comment-children{margin-top:12px;padding-left:min(var(--indent-size),var(--max-indent));border-left:2px solid var(--ion-color-light-shade)}.comment-children val-comment{display:block}.comment-children val-comment:first-of-type .comment{padding-top:0}.load-more-button,.view-replies-button{margin-top:8px;margin-left:0}ion-popover{--width: 200px}ion-popover ion-list{padding:4px 0}ion-popover ion-item{--min-height: 40px;--padding-start: 12px;font-size:.875rem}ion-popover ion-item ion-icon{font-size:1.125rem;margin-right:8px}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 2px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 2px);height:max(28px,36px - 2px);font-size:max(.6875rem,.8125rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 4px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 4px);height:max(28px,36px - 4px);font-size:max(.6875rem,.75rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 6px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 6px);height:max(28px,36px - 6px);font-size:max(.6875rem,.6875rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 8px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 8px);height:max(28px,36px - 8px);font-size:max(.6875rem,.625rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 10px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 10px);height:max(28px,36px - 10px);font-size:max(.6875rem,.5625rem)}:host-context(.dark) .comment.highlighted,:host-context(.ion-palette-dark) .comment.highlighted,:host-context(html.ion-palette-dark) .comment.highlighted,:host-context(body.dark) .comment.highlighted,:host-context([data-theme=dark]) .comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.12)}:host-context(.dark) .avatar-placeholder,:host-context(.ion-palette-dark) .avatar-placeholder,:host-context(html.ion-palette-dark) .avatar-placeholder,:host-context(body.dark) .avatar-placeholder,:host-context([data-theme=dark]) .avatar-placeholder{background:linear-gradient(135deg,var(--ion-color-primary-shade),var(--ion-color-secondary-shade))}\n"], dependencies: [{ kind: "component", type: CommentComponent, selector: "val-comment", inputs: ["props"], outputs: ["authorClick", "reactionClick", "actionClick", "menuItemClick", "loadMoreClick", "collapseToggle"] }, { 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: 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: IonPopover, selector: "ion-popover" }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: AvatarComponent, selector: "val-avatar", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
21660
+ `, isInline: true, styles: [":host{display:block}.comment{--indent-size: 24px;--max-indent: 72px;padding:12px 0}.comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.08);border-radius:8px;padding:12px;margin:-4px}.comment.pinned{border-left:3px solid var(--ion-color-primary);padding-left:12px}.comment.collapsed{opacity:.7}.pinned-indicator{display:flex;align-items:center;gap:4px;font-size:.75rem;color:var(--ion-color-primary);margin-bottom:8px;padding-left:44px}.pinned-indicator ion-icon{font-size:.875rem}.comment-main{display:flex;gap:12px}.comment-avatar{flex-shrink:0;cursor:pointer}.comment-avatar val-avatar{--size: 36px}.comment-avatar .avatar-placeholder{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--ion-color-primary),var(--ion-color-secondary));display:flex;align-items:center;justify-content:center;color:#fff;font-size:.875rem;font-weight:600}.comment-body{flex:1;min-width:0}.comment-header{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin-bottom:4px}.author-info{display:flex;align-items:center;gap:4px;cursor:pointer}.author-info:hover .author-name{text-decoration:underline}.author-name{font-weight:600;font-size:.875rem;color:var(--ion-text-color)}.verified-badge{color:var(--ion-color-primary);font-size:1rem}.author-username{font-size:.8125rem;color:var(--ion-color-medium)}.comment-meta{display:flex;align-items:center;gap:6px;margin-left:auto}.timestamp{font-size:.75rem;color:var(--ion-color-medium)}.edited-label{font-size:.6875rem;color:var(--ion-color-medium);font-style:italic}.more-button{--padding-start: 4px;--padding-end: 4px;margin:0;height:28px}.more-button ion-icon{font-size:1.125rem;color:var(--ion-color-medium)}.menu-divider{height:1px;background-color:var(--ion-color-light);margin:4px 0}.comment-content{font-size:.875rem;line-height:1.5;color:var(--ion-text-color);word-wrap:break-word;overflow-wrap:break-word}.comment-content :global(.comment-link){color:var(--ion-color-primary);text-decoration:none}.comment-content :global(.comment-link):hover{text-decoration:underline}.comment-content :global(.comment-mention){color:var(--ion-color-primary);font-weight:500;cursor:pointer}.comment-content :global(.comment-mention):hover{text-decoration:underline}.comment-content :global(.comment-hashtag){color:var(--ion-color-primary);cursor:pointer}.comment-content :global(.comment-hashtag):hover{text-decoration:underline}.comment-reactions{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}.reaction-button{display:flex;align-items:center;gap:4px;background:none;border:none;padding:4px 8px;border-radius:16px;cursor:pointer;font-size:.8125rem;color:var(--reaction-color, var(--ion-color-medium));transition:all .2s ease;background-color:transparent}.reaction-button:hover{background-color:rgba(var(--ion-color-medium-rgb),.1)}.reaction-button.active{background-color:rgba(var(--ion-color-primary-rgb),.1)}.reaction-button.active ion-icon{transform:scale(1.1)}.reaction-button ion-icon{font-size:1.125rem;transition:transform .2s ease}.reaction-button .reaction-count{font-weight:500}.comment-actions{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;margin-left:-8px}.comment-actions ion-button{--padding-start: 8px;--padding-end: 8px;font-size:.8125rem;height:32px;text-transform:none;font-weight:500}.expand-button,.collapse-button,.load-more-button,.view-replies-button{display:flex;align-items:center;gap:6px;background:none;border:none;padding:8px 12px;cursor:pointer;font-size:.8125rem;color:var(--ion-color-primary);border-radius:8px;transition:background-color .2s}.expand-button:hover,.collapse-button:hover,.load-more-button:hover,.view-replies-button:hover{background-color:rgba(var(--ion-color-primary-rgb),.1)}.expand-button ion-icon,.collapse-button ion-icon,.load-more-button ion-icon,.view-replies-button ion-icon{font-size:1rem}.expand-button{margin-top:4px;color:var(--ion-color-medium)}.comment-children{margin-top:12px;padding-left:min(var(--indent-size),var(--max-indent));border-left:2px solid var(--ion-color-light-shade)}.comment-children val-comment{display:block}.comment-children val-comment:first-of-type .comment{padding-top:0}.load-more-button,.view-replies-button{margin-top:8px;margin-left:0}ion-popover{--width: 200px}ion-popover ion-list{padding:4px 0}ion-popover ion-item{--min-height: 40px;--padding-start: 12px;font-size:.875rem}ion-popover ion-item ion-icon{font-size:1.125rem;margin-right:8px}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 2px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 2px);height:max(28px,36px - 2px);font-size:max(11px,14px - 1px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 4px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 4px);height:max(28px,36px - 4px);font-size:max(11px,14px - 2px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 6px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 6px);height:max(28px,36px - 6px);font-size:max(11px,14px - 3px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 8px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 8px);height:max(28px,36px - 8px);font-size:max(11px,14px - 4px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 10px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 10px);height:max(28px,36px - 10px);font-size:max(11px,14px - 5px)}:host-context(.dark) .comment.highlighted,:host-context(.ion-palette-dark) .comment.highlighted,:host-context(html.ion-palette-dark) .comment.highlighted,:host-context(body.dark) .comment.highlighted,:host-context([data-theme=dark]) .comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.12)}:host-context(.dark) .avatar-placeholder,:host-context(.ion-palette-dark) .avatar-placeholder,:host-context(html.ion-palette-dark) .avatar-placeholder,:host-context(body.dark) .avatar-placeholder,:host-context([data-theme=dark]) .avatar-placeholder{background:linear-gradient(135deg,var(--ion-color-primary-shade),var(--ion-color-secondary-shade))}\n"], dependencies: [{ kind: "component", type: CommentComponent, selector: "val-comment", inputs: ["props"], outputs: ["authorClick", "reactionClick", "actionClick", "menuItemClick", "loadMoreClick", "collapseToggle"] }, { 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: 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: IonPopover, selector: "ion-popover" }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: AvatarComponent, selector: "val-avatar", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
21661
21661
  }
21662
21662
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CommentComponent, decorators: [{
21663
21663
  type: Component,
@@ -21847,7 +21847,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
21847
21847
  </div>
21848
21848
  }
21849
21849
  </div>
21850
- `, styles: ["@charset \"UTF-8\";:root{--val-container-sm: 540px;--val-container-md: 720px;--val-container-lg: 880px;--val-container-xl: 1100px;--val-container-padding: 16px;--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}body.dark,html.ion-palette-dark,body[data-theme=dark]{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}:host{display:block}.comment{--indent-size: 24px;--max-indent: 72px;padding:12px 0}.comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.08);border-radius:8px;padding:12px;margin:-4px}.comment.pinned{border-left:3px solid var(--ion-color-primary);padding-left:12px}.comment.collapsed{opacity:.7}.pinned-indicator{display:flex;align-items:center;gap:4px;font-size:.75rem;color:var(--ion-color-primary);margin-bottom:8px;padding-left:44px}.pinned-indicator ion-icon{font-size:.875rem}.comment-main{display:flex;gap:12px}.comment-avatar{flex-shrink:0;cursor:pointer}.comment-avatar val-avatar{--size: 36px}.comment-avatar .avatar-placeholder{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--ion-color-primary),var(--ion-color-secondary));display:flex;align-items:center;justify-content:center;color:#fff;font-size:.875rem;font-weight:600}.comment-body{flex:1;min-width:0}.comment-header{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin-bottom:4px}.author-info{display:flex;align-items:center;gap:4px;cursor:pointer}.author-info:hover .author-name{text-decoration:underline}.author-name{font-weight:600;font-size:.875rem;color:var(--ion-text-color)}.verified-badge{color:var(--ion-color-primary);font-size:1rem}.author-username{font-size:.8125rem;color:var(--ion-color-medium)}.comment-meta{display:flex;align-items:center;gap:6px;margin-left:auto}.timestamp{font-size:.75rem;color:var(--ion-color-medium)}.edited-label{font-size:.6875rem;color:var(--ion-color-medium);font-style:italic}.more-button{--padding-start: 4px;--padding-end: 4px;margin:0;height:28px}.more-button ion-icon{font-size:1.125rem;color:var(--ion-color-medium)}.menu-divider{height:1px;background-color:var(--ion-color-light);margin:4px 0}.comment-content{font-size:.875rem;line-height:1.5;color:var(--ion-text-color);word-wrap:break-word;overflow-wrap:break-word}.comment-content :global(.comment-link){color:var(--ion-color-primary);text-decoration:none}.comment-content :global(.comment-link):hover{text-decoration:underline}.comment-content :global(.comment-mention){color:var(--ion-color-primary);font-weight:500;cursor:pointer}.comment-content :global(.comment-mention):hover{text-decoration:underline}.comment-content :global(.comment-hashtag){color:var(--ion-color-primary);cursor:pointer}.comment-content :global(.comment-hashtag):hover{text-decoration:underline}.comment-reactions{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}.reaction-button{display:flex;align-items:center;gap:4px;background:none;border:none;padding:4px 8px;border-radius:16px;cursor:pointer;font-size:.8125rem;color:var(--reaction-color, var(--ion-color-medium));transition:all .2s ease;background-color:transparent}.reaction-button:hover{background-color:rgba(var(--ion-color-medium-rgb),.1)}.reaction-button.active{background-color:rgba(var(--ion-color-primary-rgb),.1)}.reaction-button.active ion-icon{transform:scale(1.1)}.reaction-button ion-icon{font-size:1.125rem;transition:transform .2s ease}.reaction-button .reaction-count{font-weight:500}.comment-actions{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;margin-left:-8px}.comment-actions ion-button{--padding-start: 8px;--padding-end: 8px;font-size:.8125rem;height:32px;text-transform:none;font-weight:500}.expand-button,.collapse-button,.load-more-button,.view-replies-button{display:flex;align-items:center;gap:6px;background:none;border:none;padding:8px 12px;cursor:pointer;font-size:.8125rem;color:var(--ion-color-primary);border-radius:8px;transition:background-color .2s}.expand-button:hover,.collapse-button:hover,.load-more-button:hover,.view-replies-button:hover{background-color:rgba(var(--ion-color-primary-rgb),.1)}.expand-button ion-icon,.collapse-button ion-icon,.load-more-button ion-icon,.view-replies-button ion-icon{font-size:1rem}.expand-button{margin-top:4px;color:var(--ion-color-medium)}.comment-children{margin-top:12px;padding-left:min(var(--indent-size),var(--max-indent));border-left:2px solid var(--ion-color-light-shade)}.comment-children val-comment{display:block}.comment-children val-comment:first-of-type .comment{padding-top:0}.load-more-button,.view-replies-button{margin-top:8px;margin-left:0}ion-popover{--width: 200px}ion-popover ion-list{padding:4px 0}ion-popover ion-item{--min-height: 40px;--padding-start: 12px;font-size:.875rem}ion-popover ion-item ion-icon{font-size:1.125rem;margin-right:8px}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 2px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 2px);height:max(28px,36px - 2px);font-size:max(.6875rem,.8125rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 4px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 4px);height:max(28px,36px - 4px);font-size:max(.6875rem,.75rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 6px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 6px);height:max(28px,36px - 6px);font-size:max(.6875rem,.6875rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 8px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 8px);height:max(28px,36px - 8px);font-size:max(.6875rem,.625rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 10px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 10px);height:max(28px,36px - 10px);font-size:max(.6875rem,.5625rem)}:host-context(.dark) .comment.highlighted,:host-context(.ion-palette-dark) .comment.highlighted,:host-context(html.ion-palette-dark) .comment.highlighted,:host-context(body.dark) .comment.highlighted,:host-context([data-theme=dark]) .comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.12)}:host-context(.dark) .avatar-placeholder,:host-context(.ion-palette-dark) .avatar-placeholder,:host-context(html.ion-palette-dark) .avatar-placeholder,:host-context(body.dark) .avatar-placeholder,:host-context([data-theme=dark]) .avatar-placeholder{background:linear-gradient(135deg,var(--ion-color-primary-shade),var(--ion-color-secondary-shade))}\n"] }]
21850
+ `, styles: [":host{display:block}.comment{--indent-size: 24px;--max-indent: 72px;padding:12px 0}.comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.08);border-radius:8px;padding:12px;margin:-4px}.comment.pinned{border-left:3px solid var(--ion-color-primary);padding-left:12px}.comment.collapsed{opacity:.7}.pinned-indicator{display:flex;align-items:center;gap:4px;font-size:.75rem;color:var(--ion-color-primary);margin-bottom:8px;padding-left:44px}.pinned-indicator ion-icon{font-size:.875rem}.comment-main{display:flex;gap:12px}.comment-avatar{flex-shrink:0;cursor:pointer}.comment-avatar val-avatar{--size: 36px}.comment-avatar .avatar-placeholder{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--ion-color-primary),var(--ion-color-secondary));display:flex;align-items:center;justify-content:center;color:#fff;font-size:.875rem;font-weight:600}.comment-body{flex:1;min-width:0}.comment-header{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin-bottom:4px}.author-info{display:flex;align-items:center;gap:4px;cursor:pointer}.author-info:hover .author-name{text-decoration:underline}.author-name{font-weight:600;font-size:.875rem;color:var(--ion-text-color)}.verified-badge{color:var(--ion-color-primary);font-size:1rem}.author-username{font-size:.8125rem;color:var(--ion-color-medium)}.comment-meta{display:flex;align-items:center;gap:6px;margin-left:auto}.timestamp{font-size:.75rem;color:var(--ion-color-medium)}.edited-label{font-size:.6875rem;color:var(--ion-color-medium);font-style:italic}.more-button{--padding-start: 4px;--padding-end: 4px;margin:0;height:28px}.more-button ion-icon{font-size:1.125rem;color:var(--ion-color-medium)}.menu-divider{height:1px;background-color:var(--ion-color-light);margin:4px 0}.comment-content{font-size:.875rem;line-height:1.5;color:var(--ion-text-color);word-wrap:break-word;overflow-wrap:break-word}.comment-content :global(.comment-link){color:var(--ion-color-primary);text-decoration:none}.comment-content :global(.comment-link):hover{text-decoration:underline}.comment-content :global(.comment-mention){color:var(--ion-color-primary);font-weight:500;cursor:pointer}.comment-content :global(.comment-mention):hover{text-decoration:underline}.comment-content :global(.comment-hashtag){color:var(--ion-color-primary);cursor:pointer}.comment-content :global(.comment-hashtag):hover{text-decoration:underline}.comment-reactions{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}.reaction-button{display:flex;align-items:center;gap:4px;background:none;border:none;padding:4px 8px;border-radius:16px;cursor:pointer;font-size:.8125rem;color:var(--reaction-color, var(--ion-color-medium));transition:all .2s ease;background-color:transparent}.reaction-button:hover{background-color:rgba(var(--ion-color-medium-rgb),.1)}.reaction-button.active{background-color:rgba(var(--ion-color-primary-rgb),.1)}.reaction-button.active ion-icon{transform:scale(1.1)}.reaction-button ion-icon{font-size:1.125rem;transition:transform .2s ease}.reaction-button .reaction-count{font-weight:500}.comment-actions{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;margin-left:-8px}.comment-actions ion-button{--padding-start: 8px;--padding-end: 8px;font-size:.8125rem;height:32px;text-transform:none;font-weight:500}.expand-button,.collapse-button,.load-more-button,.view-replies-button{display:flex;align-items:center;gap:6px;background:none;border:none;padding:8px 12px;cursor:pointer;font-size:.8125rem;color:var(--ion-color-primary);border-radius:8px;transition:background-color .2s}.expand-button:hover,.collapse-button:hover,.load-more-button:hover,.view-replies-button:hover{background-color:rgba(var(--ion-color-primary-rgb),.1)}.expand-button ion-icon,.collapse-button ion-icon,.load-more-button ion-icon,.view-replies-button ion-icon{font-size:1rem}.expand-button{margin-top:4px;color:var(--ion-color-medium)}.comment-children{margin-top:12px;padding-left:min(var(--indent-size),var(--max-indent));border-left:2px solid var(--ion-color-light-shade)}.comment-children val-comment{display:block}.comment-children val-comment:first-of-type .comment{padding-top:0}.load-more-button,.view-replies-button{margin-top:8px;margin-left:0}ion-popover{--width: 200px}ion-popover ion-list{padding:4px 0}ion-popover ion-item{--min-height: 40px;--padding-start: 12px;font-size:.875rem}ion-popover ion-item ion-icon{font-size:1.125rem;margin-right:8px}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 2px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 2px);height:max(28px,36px - 2px);font-size:max(11px,14px - 1px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 4px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 4px);height:max(28px,36px - 4px);font-size:max(11px,14px - 2px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 6px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 6px);height:max(28px,36px - 6px);font-size:max(11px,14px - 3px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 8px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 8px);height:max(28px,36px - 8px);font-size:max(11px,14px - 4px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 10px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 10px);height:max(28px,36px - 10px);font-size:max(11px,14px - 5px)}:host-context(.dark) .comment.highlighted,:host-context(.ion-palette-dark) .comment.highlighted,:host-context(html.ion-palette-dark) .comment.highlighted,:host-context(body.dark) .comment.highlighted,:host-context([data-theme=dark]) .comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.12)}:host-context(.dark) .avatar-placeholder,:host-context(.ion-palette-dark) .avatar-placeholder,:host-context(html.ion-palette-dark) .avatar-placeholder,:host-context(body.dark) .avatar-placeholder,:host-context([data-theme=dark]) .avatar-placeholder{background:linear-gradient(135deg,var(--ion-color-primary-shade),var(--ion-color-secondary-shade))}\n"] }]
21851
21851
  }], propDecorators: { props: [{
21852
21852
  type: Input
21853
21853
  }], authorClick: [{
@@ -41737,7 +41737,7 @@ const PROFILE_VIEW_I18N = {
41737
41737
  const HANDLE_PATTERN = /^[a-z_][a-z0-9_]*$/;
41738
41738
  const HANDLE_MIN = 3;
41739
41739
  const HANDLE_MAX = 20;
41740
- const DEFAULT_NAMESPACE$f = 'Settings.Profile';
41740
+ const DEFAULT_NAMESPACE$g = 'Settings.Profile';
41741
41741
  /**
41742
41742
  * `val-profile-view` — vista Perfil full-feature autocontenida (organism).
41743
41743
  *
@@ -41787,7 +41787,7 @@ class ProfileViewComponent {
41787
41787
  showPhone: merged.showPhone ?? true,
41788
41788
  showShareCta: merged.showShareCta ?? true,
41789
41789
  profileBaseUrl: merged.profileBaseUrl ?? '',
41790
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$f,
41790
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$g,
41791
41791
  onSaved: merged.onSaved,
41792
41792
  onAvatarUploaded: merged.onAvatarUploaded,
41793
41793
  };
@@ -42263,12 +42263,17 @@ class ProfileViewComponent {
42263
42263
 
42264
42264
  <val-form #form [props]="formMeta" (onSubmit)="onSubmit($event)" />
42265
42265
 
42266
- <!-- Card de compartir + modal QR removidos temporalmente (se retoman luego).
42267
- Wiring intacto: shareCta(), shareModal(), shareOpen, showShareCta. -->
42266
+ @if (resolvedConfig().showShareCta && p.handle) {
42267
+ <section class="settings-section share-cta-section">
42268
+ <val-cta-card [props]="shareCta()" (onAction)="shareOpen.set(true)" />
42269
+ </section>
42270
+ }
42268
42271
  }
42269
42272
  }
42270
42273
  </div>
42271
- `, isInline: true, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.page-header h1{font-size:22px;font-weight:700;margin:0 0 4px}.hint{font-size:13px;opacity:.7;margin:0 0 12px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.avatar-section{display:flex;align-items:center;gap:16px}.avatar-meta{display:flex;flex-direction:column;gap:2px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: AvatarUploadComponent, selector: "val-avatar-upload", inputs: ["props"], outputs: ["uploaded", "error", "uploadStart"] }, { kind: "component", type: EmptyStateComponent, selector: "val-empty-state", inputs: ["props"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: SkeletonLayoutComponent, selector: "val-skeleton-layout", inputs: ["props"] }] }); }
42274
+
42275
+ <val-share-profile-modal [isOpen]="shareOpen()" [props]="shareModal()" (dismissed)="shareOpen.set(false)" />
42276
+ `, isInline: true, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.page-header h1{font-size:22px;font-weight:700;margin:0 0 4px}.hint{font-size:13px;opacity:.7;margin:0 0 12px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.avatar-section{display:flex;align-items:center;gap:16px}.avatar-meta{display:flex;flex-direction:column;gap:2px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: AvatarUploadComponent, selector: "val-avatar-upload", inputs: ["props"], outputs: ["uploaded", "error", "uploadStart"] }, { kind: "component", type: EmptyStateComponent, selector: "val-empty-state", inputs: ["props"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: SkeletonLayoutComponent, selector: "val-skeleton-layout", inputs: ["props"] }, { kind: "component", type: CtaCardComponent, selector: "val-cta-card", inputs: ["props"], outputs: ["onAction"] }, { kind: "component", type: ShareProfileModalComponent, selector: "val-share-profile-modal", inputs: ["isOpen", "props"], outputs: ["dismissed", "shared"] }] }); }
42272
42277
  }
42273
42278
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ProfileViewComponent, decorators: [{
42274
42279
  type: Component,
@@ -42344,11 +42349,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
42344
42349
 
42345
42350
  <val-form #form [props]="formMeta" (onSubmit)="onSubmit($event)" />
42346
42351
 
42347
- <!-- Card de compartir + modal QR removidos temporalmente (se retoman luego).
42348
- Wiring intacto: shareCta(), shareModal(), shareOpen, showShareCta. -->
42352
+ @if (resolvedConfig().showShareCta && p.handle) {
42353
+ <section class="settings-section share-cta-section">
42354
+ <val-cta-card [props]="shareCta()" (onAction)="shareOpen.set(true)" />
42355
+ </section>
42356
+ }
42349
42357
  }
42350
42358
  }
42351
42359
  </div>
42360
+
42361
+ <val-share-profile-modal [isOpen]="shareOpen()" [props]="shareModal()" (dismissed)="shareOpen.set(false)" />
42352
42362
  `, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.page-header h1{font-size:22px;font-weight:700;margin:0 0 4px}.hint{font-size:13px;opacity:.7;margin:0 0 12px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.avatar-section{display:flex;align-items:center;gap:16px}.avatar-meta{display:flex;flex-direction:column;gap:2px}\n"] }]
42353
42363
  }], ctorParameters: () => [], propDecorators: { config: [{
42354
42364
  type: Input
@@ -42479,21 +42489,6 @@ class PreferencesService {
42479
42489
  this._updateInFlight = false;
42480
42490
  const destroyRef = inject(DestroyRef);
42481
42491
  destroyRef.onDestroy(() => this.unbind());
42482
- // Seed `_fontSize` con el valor que FontSizeService ya restauró desde
42483
- // localStorage en su constructor (providedIn root → ya corrió al inyectarlo
42484
- // acá). Sin esto, el signal arranca en 'medium' y el effect de abajo —que
42485
- // dispara al marcarse `synced`— forzaría 'medium' pisando el tamaño
42486
- // restaurado cuando el snapshot Firestore NO trae `fontSize` (user sin ese
42487
- // campo en el mirror). Bug: cambias el tamaño, refrescas y se pierde.
42488
- if (this.fontSizeService) {
42489
- this._fontSize.set(this.fontSizeService.size());
42490
- }
42491
- // Mismo seed para theme — ThemeService aplica su valor de localStorage en
42492
- // bootstrap (`currentOption`). Necesario para desgatear el effect de theme
42493
- // (abajo) sin que el default 'auto' pise el tema restaurado.
42494
- if (this.themeService) {
42495
- this._theme.set(this.themeService.currentOption);
42496
- }
42497
42492
  // Auto-bind al user actual. effect() corre en injection context (constructor OK).
42498
42493
  // allowSignalWrites: bindToUser/unbind escriben `_synced` (señal interna sin
42499
42494
  // dependencias del propio effect) → seguro y necesario.
@@ -42506,18 +42501,14 @@ class PreferencesService {
42506
42501
  this.unbind();
42507
42502
  }
42508
42503
  }, { allowSignalWrites: true });
42509
- // Side-effect: aplicar theme. SIN gate `_synced` (local-first, igual que
42510
- // fontSize): ThemeService/localStorage es la verdad del dispositivo y
42511
- // `_theme` viene seedeado arriba. Aplicar siempre que cambie da apply
42512
- // optimista al instante; el sync cross-device también aplica vía snapshot.
42513
- // Gatearlo en `_synced` hacía que el primer cambio de un user nuevo (sin doc
42514
- // Firestore aún) no se aplicara: el selector mostraba "Dark" pero el tema no
42515
- // cambiaba. (El selector de idioma no sufre esto porque val-language-selector
42516
- // llama i18n.setLanguage directo; el de theme depende solo de este effect.)
42517
- // OJO: usar `toggleUserPreference()` y NO el setter `Theme =`. El setter solo
42518
- // persiste localStorage + emite el BehaviorSubject; no aplica las clases
42519
- // `body.dark`/`html.ion-palette-dark` que el palette de Ionic necesita.
42504
+ // Side-effect: aplicar theme local cuando llega snapshot real.
42505
+ // OJO: usar `toggleUserPreference()` y NO el setter `Theme =`. El setter
42506
+ // solo persiste localStorage + emite el BehaviorSubject; no aplica las
42507
+ // clases `body.dark`/`body.light` que el palette `dark.class.css` de
42508
+ // Ionic necesita para conmutar el tema visible.
42520
42509
  effect(() => {
42510
+ if (!this._synced())
42511
+ return;
42521
42512
  const t = this._theme();
42522
42513
  if (this.themeService) {
42523
42514
  this.themeService.toggleUserPreference(t);
@@ -42532,17 +42523,11 @@ class PreferencesService {
42532
42523
  this.i18n.setLanguage(l);
42533
42524
  }
42534
42525
  });
42535
- // Side-effect: aplicar fontSize.
42536
- // SIN gate `_synced` (igual que theme): el tamaño es una
42537
- // preferencia local-first — FontSizeService/localStorage es la verdad del
42538
- // dispositivo y `_fontSize` ya viene seedeado de ahí arriba. Aplicar siempre
42539
- // que cambie permite que el cambio del usuario surta efecto al instante
42540
- // (optimista, sin esperar el round-trip a Firestore) y que el sync
42541
- // cross-device también aplique al llegar el snapshot. Gatearlo en `_synced`
42542
- // hacía que el primer cambio de un user nuevo (sin doc Firestore aún) no se
42543
- // aplicara: el selector mostraba "Grande" pero el font-size no cambiaba.
42526
+ // Side-effect: aplicar fontSize local cuando llega snapshot real.
42544
42527
  // allowSignalWrites: fontSizeService.setSize() escribe this._size (signal).
42545
42528
  effect(() => {
42529
+ if (!this._synced())
42530
+ return;
42546
42531
  const fs = this._fontSize();
42547
42532
  if (this.fontSizeService) {
42548
42533
  this.fontSizeService.setSize(fs);
@@ -42718,7 +42703,7 @@ const PREFERENCES_VIEW_I18N = {
42718
42703
  },
42719
42704
  };
42720
42705
 
42721
- const DEFAULT_NAMESPACE$e = 'Settings.General';
42706
+ const DEFAULT_NAMESPACE$f = 'Settings.General';
42722
42707
  const DEFAULT_LANGUAGES = ['es', 'en'];
42723
42708
  /**
42724
42709
  * `val-preferences-view` — vista de preferencias de UI cross-app (tema +
@@ -42755,7 +42740,7 @@ class PreferencesViewComponent {
42755
42740
  showLanguage: merged.showLanguage ?? true,
42756
42741
  showFontSize: merged.showFontSize ?? true,
42757
42742
  supportedLanguages: merged.supportedLanguages ?? DEFAULT_LANGUAGES,
42758
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$e,
42743
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$f,
42759
42744
  onThemeChanged: merged.onThemeChanged,
42760
42745
  onLanguageChanged: merged.onLanguageChanged,
42761
42746
  onFontSizeChanged: merged.onFontSizeChanged,
@@ -44511,7 +44496,7 @@ const SECURITY_VIEW_I18N = {
44511
44496
  };
44512
44497
 
44513
44498
  addIcons({ laptopOutline, phonePortraitOutline });
44514
- const DEFAULT_NAMESPACE$d = 'Settings.Security';
44499
+ const DEFAULT_NAMESPACE$e = 'Settings.Security';
44515
44500
  /**
44516
44501
  * `val-security-view` — vista Seguridad full-feature autocontenida (organism).
44517
44502
  *
@@ -44560,7 +44545,7 @@ class SecurityViewComponent {
44560
44545
  showChangeEmail: merged.showChangeEmail ?? true,
44561
44546
  showMfa: merged.showMfa ?? true,
44562
44547
  showPolicies: merged.showPolicies ?? true,
44563
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$d,
44548
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$e,
44564
44549
  handleDeepLinks: merged.handleDeepLinks ?? true,
44565
44550
  homeRoute: merged.homeRoute ?? '/app/home',
44566
44551
  onMfaCompleted: merged.onMfaCompleted,
@@ -45466,7 +45451,7 @@ const CREATE_ORG_MODAL_I18N = {
45466
45451
  },
45467
45452
  };
45468
45453
 
45469
- const DEFAULT_NAMESPACE$c = 'CreateOrgModal';
45454
+ const DEFAULT_NAMESPACE$d = 'CreateOrgModal';
45470
45455
  /**
45471
45456
  * `val-create-org-modal` — modal de creación de organización (organism
45472
45457
  * compartido). Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por
@@ -45492,10 +45477,10 @@ class CreateOrgModalComponent {
45492
45477
  this.toast = inject(ToastService);
45493
45478
  this.errors = inject(ValtechErrorService);
45494
45479
  /** Namespace i18n con que la vista resuelve sus textos. */
45495
- this.i18nNamespace = DEFAULT_NAMESPACE$c;
45480
+ this.i18nNamespace = DEFAULT_NAMESPACE$d;
45496
45481
  this._busy = signal(false);
45497
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$c)) {
45498
- this.i18n.registerContent(DEFAULT_NAMESPACE$c, CREATE_ORG_MODAL_I18N);
45482
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$d)) {
45483
+ this.i18n.registerContent(DEFAULT_NAMESPACE$d, CREATE_ORG_MODAL_I18N);
45499
45484
  }
45500
45485
  this.formMeta = signal(this.buildFormMeta());
45501
45486
  effect(() => {
@@ -45731,7 +45716,7 @@ const DELETE_ACCOUNT_MODAL_I18N = {
45731
45716
  },
45732
45717
  };
45733
45718
 
45734
- const DEFAULT_NAMESPACE$b = 'Settings.DeleteAccount';
45719
+ const DEFAULT_NAMESPACE$c = 'Settings.DeleteAccount';
45735
45720
  /**
45736
45721
  * `val-delete-account-modal` — modal de eliminación de cuenta (organism,
45737
45722
  * cuenta-específico). Promovido desde `showcase` bajo el proceso de ADR-021.
@@ -45757,7 +45742,7 @@ class DeleteAccountModalComponent {
45757
45742
  this.i18n = inject(I18nService);
45758
45743
  this.toast = inject(ToastService);
45759
45744
  /** Namespace i18n con que la vista resuelve sus textos. */
45760
- this.i18nNamespace = DEFAULT_NAMESPACE$b;
45745
+ this.i18nNamespace = DEFAULT_NAMESPACE$c;
45761
45746
  this.hasPassword = signal(null);
45762
45747
  this.codeSent = signal(false);
45763
45748
  this.formState = signal(ComponentStates.ENABLED);
@@ -45824,8 +45809,8 @@ class DeleteAccountModalComponent {
45824
45809
  ],
45825
45810
  actions: this.submitBtn('delete-code-submit', 'confirmCta'),
45826
45811
  }));
45827
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$b)) {
45828
- this.i18n.registerContent(DEFAULT_NAMESPACE$b, DELETE_ACCOUNT_MODAL_I18N);
45812
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$c)) {
45813
+ this.i18n.registerContent(DEFAULT_NAMESPACE$c, DELETE_ACCOUNT_MODAL_I18N);
45829
45814
  }
45830
45815
  }
45831
45816
  ngOnInit() {
@@ -46026,7 +46011,7 @@ const ORG_INFO_SHEET_I18N = {
46026
46011
  },
46027
46012
  };
46028
46013
 
46029
- const DEFAULT_NAMESPACE$a = 'OrgInfoSheet';
46014
+ const DEFAULT_NAMESPACE$b = 'OrgInfoSheet';
46030
46015
  /**
46031
46016
  * `val-org-info-sheet` — sheet informativo "¿Qué son las organizaciones?".
46032
46017
  * Extraído del `OrgInfoSheetComponent` inline de `AccountPage` bajo el proceso de
@@ -46071,15 +46056,15 @@ class OrgInfoSheetComponent {
46071
46056
  text: this.closeProps.text || this.resolvedCloseLabel(),
46072
46057
  }));
46073
46058
  addIcons({ peopleOutline, swapHorizontalOutline, mailOutline });
46074
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$a)) {
46075
- this.i18n.registerContent(DEFAULT_NAMESPACE$a, ORG_INFO_SHEET_I18N);
46059
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$b)) {
46060
+ this.i18n.registerContent(DEFAULT_NAMESPACE$b, ORG_INFO_SHEET_I18N);
46076
46061
  }
46077
46062
  }
46078
46063
  dismiss() {
46079
46064
  this._modalRef?.dismiss(null, 'cancel');
46080
46065
  }
46081
46066
  tt(key) {
46082
- return this.i18n.t(key, DEFAULT_NAMESPACE$a);
46067
+ return this.i18n.t(key, DEFAULT_NAMESPACE$b);
46083
46068
  }
46084
46069
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OrgInfoSheetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
46085
46070
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: OrgInfoSheetComponent, isStandalone: true, selector: "val-org-info-sheet", inputs: { title: "title", subtitle: "subtitle", body: "body", closeLabel: "closeLabel", closeProps: "closeProps", _modalRef: "_modalRef" }, ngImport: i0, template: `
@@ -46246,7 +46231,7 @@ const SWITCH_ORG_MODAL_I18N = {
46246
46231
  },
46247
46232
  };
46248
46233
 
46249
- const DEFAULT_NAMESPACE$9 = 'Settings.SwitchOrg';
46234
+ const DEFAULT_NAMESPACE$a = 'Settings.SwitchOrg';
46250
46235
  addIcons({ businessOutline, checkmarkCircleOutline });
46251
46236
  /**
46252
46237
  * `val-switch-org-modal` — modal de cambio de organización activa (organism
@@ -46272,7 +46257,7 @@ class SwitchOrgModalComponent {
46272
46257
  this.orgSwitch = inject(OrgSwitchService);
46273
46258
  this.errors = inject(ValtechErrorService);
46274
46259
  /** Namespace i18n con que la vista resuelve sus textos. */
46275
- this.i18nNamespace = DEFAULT_NAMESPACE$9;
46260
+ this.i18nNamespace = DEFAULT_NAMESPACE$a;
46276
46261
  this.orgs = signal([]);
46277
46262
  this.loading = signal(false);
46278
46263
  this.query = signal('');
@@ -46288,8 +46273,8 @@ class SwitchOrgModalComponent {
46288
46273
  return list;
46289
46274
  return list.filter(o => o.name.toLowerCase().includes(q));
46290
46275
  });
46291
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$9)) {
46292
- this.i18n.registerContent(DEFAULT_NAMESPACE$9, SWITCH_ORG_MODAL_I18N);
46276
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$a)) {
46277
+ this.i18n.registerContent(DEFAULT_NAMESPACE$a, SWITCH_ORG_MODAL_I18N);
46293
46278
  }
46294
46279
  this.loadOrgs();
46295
46280
  }
@@ -46607,7 +46592,7 @@ const ACCOUNT_VIEW_I18N = {
46607
46592
  };
46608
46593
 
46609
46594
  addIcons({ peopleOutline, businessOutline, chevronForwardOutline, swapHorizontalOutline, mailOutline });
46610
- const DEFAULT_NAMESPACE$8 = 'Settings.Account';
46595
+ const DEFAULT_NAMESPACE$9 = 'Settings.Account';
46611
46596
  const DEFAULT_MANAGE_ORG_ROUTE = '/app/settings/organization';
46612
46597
  /**
46613
46598
  * `val-account-view` — vista Cuenta full-feature autocontenida (organism).
@@ -46648,7 +46633,7 @@ class AccountViewComponent {
46648
46633
  showNewOrgCta: merged.showNewOrgCta ?? true,
46649
46634
  showLogout: merged.showLogout ?? true,
46650
46635
  showDeleteAccount: merged.showDeleteAccount ?? true,
46651
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$8,
46636
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$9,
46652
46637
  manageOrgRoute: merged.manageOrgRoute ?? DEFAULT_MANAGE_ORG_ROUTE,
46653
46638
  onOrgSwitched: merged.onOrgSwitched,
46654
46639
  onOrgCreated: merged.onOrgCreated,
@@ -47373,7 +47358,7 @@ const EDIT_ORG_MODAL_I18N = {
47373
47358
  },
47374
47359
  };
47375
47360
 
47376
- const DEFAULT_NAMESPACE$7 = 'EditOrgModal';
47361
+ const DEFAULT_NAMESPACE$8 = 'EditOrgModal';
47377
47362
  /**
47378
47363
  * `val-edit-org-modal` — modal de edición de organización (nombre + descripción).
47379
47364
  * Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por la vista de
@@ -47400,10 +47385,10 @@ class EditOrgModalComponent {
47400
47385
  /** Organización a editar (pasada por `componentProps`). */
47401
47386
  this.org = null;
47402
47387
  /** Namespace i18n con que la vista resuelve sus textos. */
47403
- this.i18nNamespace = DEFAULT_NAMESPACE$7;
47388
+ this.i18nNamespace = DEFAULT_NAMESPACE$8;
47404
47389
  this.saving = signal(false);
47405
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$7)) {
47406
- this.i18n.registerContent(DEFAULT_NAMESPACE$7, EDIT_ORG_MODAL_I18N);
47390
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$8)) {
47391
+ this.i18n.registerContent(DEFAULT_NAMESPACE$8, EDIT_ORG_MODAL_I18N);
47407
47392
  }
47408
47393
  this.formMeta = signal(this.buildFormMeta());
47409
47394
  effect(() => {
@@ -47641,7 +47626,7 @@ const INVITE_MEMBER_MODAL_I18N = {
47641
47626
  },
47642
47627
  };
47643
47628
 
47644
- const DEFAULT_NAMESPACE$6 = 'InviteModal';
47629
+ const DEFAULT_NAMESPACE$7 = 'InviteModal';
47645
47630
  addIcons({ personOutline, mailOutline, closeOutline, checkmarkCircleOutline });
47646
47631
  /**
47647
47632
  * `val-invite-member-modal` — invitación de miembros a una organización.
@@ -47672,7 +47657,7 @@ class InviteMemberModalComponent {
47672
47657
  /** Ids de miembros ya existentes (excluidos de los resultados). */
47673
47658
  this.existingMemberIds = [];
47674
47659
  /** Namespace i18n con que la vista resuelve sus textos. */
47675
- this.i18nNamespace = DEFAULT_NAMESPACE$6;
47660
+ this.i18nNamespace = DEFAULT_NAMESPACE$7;
47676
47661
  this.query = signal('');
47677
47662
  this.searching = signal(false);
47678
47663
  this.searchResults = signal([]);
@@ -47719,8 +47704,8 @@ class InviteMemberModalComponent {
47719
47704
  state: this.sending() ? ComponentStates.WORKING : ComponentStates.ENABLED,
47720
47705
  }));
47721
47706
  this.searchSubject = new Subject();
47722
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$6)) {
47723
- this.i18n.registerContent(DEFAULT_NAMESPACE$6, INVITE_MEMBER_MODAL_I18N);
47707
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$7)) {
47708
+ this.i18n.registerContent(DEFAULT_NAMESPACE$7, INVITE_MEMBER_MODAL_I18N);
47724
47709
  }
47725
47710
  this.loadRoles();
47726
47711
  this.roleSub = this.roleControl.valueChanges.subscribe(v => {
@@ -48209,7 +48194,7 @@ const MEMBER_DETAIL_MODAL_I18N = {
48209
48194
  },
48210
48195
  };
48211
48196
 
48212
- const DEFAULT_NAMESPACE$5 = 'Settings.MemberDetail';
48197
+ const DEFAULT_NAMESPACE$6 = 'Settings.MemberDetail';
48213
48198
  /**
48214
48199
  * `val-member-detail-modal` — detalle de miembro de organización: perfil, rol y
48215
48200
  * permisos. Promovido desde `showcase` bajo el proceso de ADR-021.
@@ -48246,7 +48231,7 @@ class MemberDetailModalComponent {
48246
48231
  { id: 'admin', name: 'admin' },
48247
48232
  ];
48248
48233
  /** Namespace i18n con que la vista resuelve sus textos. */
48249
- this.i18nNamespace = DEFAULT_NAMESPACE$5;
48234
+ this.i18nNamespace = DEFAULT_NAMESPACE$6;
48250
48235
  this.working = signal(false);
48251
48236
  this.currentRole = signal('');
48252
48237
  this.roleControl = new FormControl('');
@@ -48286,8 +48271,8 @@ class MemberDetailModalComponent {
48286
48271
  type: 'button',
48287
48272
  state: this.working() ? ComponentStates.WORKING : ComponentStates.ENABLED,
48288
48273
  }));
48289
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$5)) {
48290
- this.i18n.registerContent(DEFAULT_NAMESPACE$5, MEMBER_DETAIL_MODAL_I18N);
48274
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$6)) {
48275
+ this.i18n.registerContent(DEFAULT_NAMESPACE$6, MEMBER_DETAIL_MODAL_I18N);
48291
48276
  }
48292
48277
  }
48293
48278
  ngOnInit() {
@@ -48624,7 +48609,7 @@ const TRANSFER_OWNERSHIP_MODAL_I18N = {
48624
48609
  },
48625
48610
  };
48626
48611
 
48627
- const DEFAULT_NAMESPACE$4 = 'Settings.TransferModal';
48612
+ const DEFAULT_NAMESPACE$5 = 'Settings.TransferModal';
48628
48613
  /**
48629
48614
  * `val-transfer-ownership-modal` — selector de miembro para transferir la
48630
48615
  * propiedad de la organización. Promovido desde `showcase` bajo el proceso de
@@ -48648,7 +48633,7 @@ class TransferOwnershipModalComponent {
48648
48633
  /** Lista de miembros transferibles (todos excepto el owner actual). */
48649
48634
  this.members = [];
48650
48635
  /** Namespace i18n con que la vista resuelve sus textos. */
48651
- this.i18nNamespace = DEFAULT_NAMESPACE$4;
48636
+ this.i18nNamespace = DEFAULT_NAMESPACE$5;
48652
48637
  this.query = signal('');
48653
48638
  this.selectedId = signal('');
48654
48639
  this.filtered = computed(() => {
@@ -48660,8 +48645,8 @@ class TransferOwnershipModalComponent {
48660
48645
  m.userId.toLowerCase().includes(q));
48661
48646
  });
48662
48647
  this.noResultsLabel = computed(() => `${this.t('noResults')} "${this.query()}"`);
48663
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$4)) {
48664
- this.i18n.registerContent(DEFAULT_NAMESPACE$4, TRANSFER_OWNERSHIP_MODAL_I18N);
48648
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$5)) {
48649
+ this.i18n.registerContent(DEFAULT_NAMESPACE$5, TRANSFER_OWNERSHIP_MODAL_I18N);
48665
48650
  }
48666
48651
  }
48667
48652
  ngOnInit() {
@@ -48994,7 +48979,7 @@ const ORGANIZATION_VIEW_I18N = {
48994
48979
  },
48995
48980
  };
48996
48981
 
48997
- const DEFAULT_NAMESPACE$3 = 'Settings.Organization';
48982
+ const DEFAULT_NAMESPACE$4 = 'Settings.Organization';
48998
48983
  const DEFAULT_VIEW_PERMISSIONS_ROUTE = '/app/settings/permissions';
48999
48984
  const DEFAULT_LEAVE_REDIRECT_ROUTE = '/app/settings/account';
49000
48985
  /**
@@ -49051,7 +49036,7 @@ class OrganizationViewComponent {
49051
49036
  showInvite: merged.showInvite ?? true,
49052
49037
  showTransferOwnership: merged.showTransferOwnership ?? true,
49053
49038
  showLeave: merged.showLeave ?? true,
49054
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$3,
49039
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$4,
49055
49040
  viewPermissionsRoute: merged.viewPermissionsRoute ?? DEFAULT_VIEW_PERMISSIONS_ROUTE,
49056
49041
  leaveRedirectRoute: merged.leaveRedirectRoute ?? DEFAULT_LEAVE_REDIRECT_ROUTE,
49057
49042
  onOrgEdited: merged.onOrgEdited,
@@ -50138,7 +50123,7 @@ addIcons({
50138
50123
  notificationsOutline,
50139
50124
  refreshOutline,
50140
50125
  });
50141
- const DEFAULT_NAMESPACE$2 = 'Settings.Notifications';
50126
+ const DEFAULT_NAMESPACE$3 = 'Settings.Notifications';
50142
50127
  /**
50143
50128
  * `val-notification-preferences-view` — **preferencias de notificaciones push**
50144
50129
  * (FCM) full-feature autocontenida (organism). Promovida desde `showcase` bajo el
@@ -50181,7 +50166,7 @@ class NotificationPreferencesViewComponent {
50181
50166
  const merged = { ...fromRoute, ...(this.config ?? {}) };
50182
50167
  return {
50183
50168
  showDetails: merged.showDetails ?? true,
50184
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$2,
50169
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$3,
50185
50170
  onPushEnabled: merged.onPushEnabled,
50186
50171
  onPushDisabled: merged.onPushDisabled,
50187
50172
  };
@@ -50952,7 +50937,7 @@ const PERMISSIONS_VIEW_I18N = {
50952
50937
  },
50953
50938
  };
50954
50939
 
50955
- const DEFAULT_NAMESPACE$1 = 'Settings.Permissions';
50940
+ const DEFAULT_NAMESPACE$2 = 'Settings.Permissions';
50956
50941
  /**
50957
50942
  * `val-permissions-view` — vista Permisos full-feature autocontenida (organism).
50958
50943
  * Visor RBAC de solo lectura: muestra los roles del sistema (Visor/Editor/Admin +
@@ -50992,7 +50977,7 @@ class PermissionsViewComponent {
50992
50977
  return {
50993
50978
  showSystemRoles: merged.showSystemRoles ?? true,
50994
50979
  showCustomRoles: merged.showCustomRoles ?? true,
50995
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$1,
50980
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$2,
50996
50981
  };
50997
50982
  });
50998
50983
  this.loading = signal(false);
@@ -51057,16 +51042,13 @@ class PermissionsViewComponent {
51057
51042
  for (const perm of role.permissions) {
51058
51043
  const [resource, action] = perm.split(':');
51059
51044
  const entry = byResource.get(resource);
51060
- // Sin entrada en el catálogo (el backend omite los recursos internal para
51061
- // no-platform-admins) o entrada internal NUNCA se muestra a nivel de
51062
- // producto. Defensa en profundidad junto al filtro del backend (ADR-024).
51063
- // Nota: el rol puede traer el permiso aunque el recurso esté filtrado del
51064
- // catálogo (ej. admin tiene templates:*); por eso se descarta lo no resuelto.
51065
- if (!entry || entry.scope === 'internal') {
51045
+ // internal (operación de plataforma) NUNCA se muestra a nivel de producto
51046
+ // defensa en profundidad junto al filtro del backend (ADR-024).
51047
+ if (entry?.scope === 'internal') {
51066
51048
  continue;
51067
51049
  }
51068
51050
  const label = this.permLabel(resource, action);
51069
- if (entry.scope === 'app') {
51051
+ if (entry?.scope === 'app') {
51070
51052
  if (entry.appId === currentAppId) {
51071
51053
  appPerms.push(label);
51072
51054
  }
@@ -51078,7 +51060,7 @@ class PermissionsViewComponent {
51078
51060
  }
51079
51061
  }
51080
51062
  else {
51081
- // scope === 'org' → gestión de la organización del cliente
51063
+ // org o desconocido → gestión de la organización del cliente
51082
51064
  orgPerms.push(label);
51083
51065
  }
51084
51066
  }
@@ -51769,7 +51751,7 @@ addIcons({
51769
51751
  openOutline,
51770
51752
  personOutline,
51771
51753
  });
51772
- const DEFAULT_NAMESPACE = 'Notifications';
51754
+ const DEFAULT_NAMESPACE$1 = 'Notifications';
51773
51755
  const DEFAULT_SETTINGS_PATH = '/app/settings/notifications';
51774
51756
  const DEFAULT_PAGE_SIZE = 10;
51775
51757
  const DEFAULT_MAX_LOAD = 50;
@@ -51829,7 +51811,7 @@ class NotificationsViewComponent {
51829
51811
  showPushOffCta: merged.showPushOffCta ?? true,
51830
51812
  enableMarkAll: merged.enableMarkAll ?? true,
51831
51813
  settingsNotificationsPath: merged.settingsNotificationsPath ?? DEFAULT_SETTINGS_PATH,
51832
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE,
51814
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$1,
51833
51815
  pageSize: merged.pageSize ?? DEFAULT_PAGE_SIZE,
51834
51816
  maxLoad: merged.maxLoad ?? DEFAULT_MAX_LOAD,
51835
51817
  onNotificationOpen: merged.onNotificationOpen,
@@ -52497,6 +52479,797 @@ function provideValtechNotificationsRoutes(opts) {
52497
52479
  ];
52498
52480
  }
52499
52481
 
52482
+ /**
52483
+ * Defaults i18n del componente (es/en). El componente los auto-registra en su
52484
+ * constructor si el consumer no proveyó el namespace `ContentReaction`. Esto
52485
+ * garantiza que NUNCA haya una key faltante: una key faltante se evalúa en el
52486
+ * template por cada change-detection → `console.warn` por CD → con el
52487
+ * `val-debug-console` activo (espeja console.* a un signal) se dispara un loop
52488
+ * infinito que congela el navegador en dev. Auto-registrar lo elimina de raíz.
52489
+ */
52490
+ const CONTENT_REACTION_I18N = {
52491
+ es: {
52492
+ question: '¿Te resultó útil este contenido?',
52493
+ commentPlaceholder: 'Cuéntanos más (opcional)...',
52494
+ submit: 'Enviar opinión',
52495
+ update: 'Actualizar opinión',
52496
+ thankYou: '¡Gracias por tu opinión!',
52497
+ submitted: 'Tu opinión ha sido registrada',
52498
+ errorSubmitting: 'Error al enviar. Intenta de nuevo.',
52499
+ positiveLabel: 'Sí, mucho',
52500
+ negativeLabel: 'No tanto',
52501
+ negativeFeedbackTitle: '¿Cuéntanos qué no te gustó?',
52502
+ },
52503
+ en: {
52504
+ question: 'Was this content helpful?',
52505
+ commentPlaceholder: 'Tell us more (optional)...',
52506
+ submit: 'Send feedback',
52507
+ update: 'Update feedback',
52508
+ thankYou: 'Thanks for your feedback!',
52509
+ submitted: 'Your feedback has been recorded',
52510
+ errorSubmitting: 'Failed to send. Please try again.',
52511
+ positiveLabel: 'Yes, a lot',
52512
+ negativeLabel: 'Not really',
52513
+ negativeFeedbackTitle: "Tell us what you didn't like?",
52514
+ },
52515
+ };
52516
+ /**
52517
+ * Componente para reacciones de contenido con emojis.
52518
+ *
52519
+ * @example
52520
+ * ```html
52521
+ * <val-content-reaction
52522
+ * [props]="{
52523
+ * entityRef: { entityType: 'article', entityId: 'art-123' },
52524
+ * question: '¿Te fue útil este artículo?'
52525
+ * }"
52526
+ * (reactionSubmit)="onReactionSubmit($event)"
52527
+ * />
52528
+ * ```
52529
+ */
52530
+ class ContentReactionComponent {
52531
+ constructor() {
52532
+ this.feedbackService = inject(FeedbackService);
52533
+ this.toast = inject(ToastService);
52534
+ this.i18n = inject(I18nService);
52535
+ this.auth = inject(AuthService, { optional: true });
52536
+ this.isConfigured = computed(() => this.feedbackService.isConfigured());
52537
+ this.props = {};
52538
+ this.reactionSubmit = new EventEmitter();
52539
+ this.reactionChange = new EventEmitter();
52540
+ // Estado reactivo
52541
+ this.state = signal({
52542
+ selectedValue: null,
52543
+ comment: '',
52544
+ isLoading: false,
52545
+ isSubmitted: false,
52546
+ hadPreviousReaction: false,
52547
+ error: null,
52548
+ });
52549
+ // Valores por defecto
52550
+ this.defaultEmojis = ['😞', '😐', '😊'];
52551
+ this.defaultLabels = ['No me ayudó', 'Regular', 'Muy útil'];
52552
+ // Computed properties
52553
+ this.resolvedProps = computed(() => ({
52554
+ entityRef: this.props.entityRef,
52555
+ question: this.props.question || this.t('question'),
52556
+ showComment: this.props.showComment ?? true,
52557
+ commentOnValues: this.props.commentOnValues,
52558
+ commentPlaceholder: this.props.commentPlaceholder || this.t('commentPlaceholder'),
52559
+ maxCommentLength: this.props.maxCommentLength ?? 500,
52560
+ emojis: this.props.emojis || this.defaultEmojis,
52561
+ emojiLabels: this.props.emojiLabels || this.defaultLabels,
52562
+ showThankYou: this.props.showThankYou ?? true,
52563
+ thankYouMessage: this.props.thankYouMessage || this.t('thankYou'),
52564
+ disabled: this.props.disabled ?? false,
52565
+ readonly: this.props.readonly ?? false,
52566
+ // Variante: 'emoji' cuando se declara explícitamente o cuando se pasan emojis custom.
52567
+ variant: this.props.variant ?? (this.props.emojis ? 'emoji' : 'buttons'),
52568
+ }));
52569
+ this.activeReactionValues = computed(() => this.resolvedProps().emojis.length === 2 ? ['negative', 'positive'] : ['negative', 'neutral', 'positive']);
52570
+ this.showCommentField = computed(() => {
52571
+ const sel = this.state().selectedValue;
52572
+ if (!sel)
52573
+ return false;
52574
+ const commentOn = this.resolvedProps().commentOnValues;
52575
+ if (commentOn !== undefined)
52576
+ return commentOn.includes(sel);
52577
+ return this.resolvedProps().showComment;
52578
+ });
52579
+ this.canSubmit = computed(() => this.state().selectedValue !== null && !this.state().isLoading);
52580
+ // Auto-registro de defaults i18n (es/en) si el consumer no proveyó el
52581
+ // namespace. Garantiza que `t()` nunca caiga en key faltante. Ver
52582
+ // CONTENT_REACTION_I18N. Idempotente: solo la 1ª instancia registra.
52583
+ if (!this.i18n.hasNamespace('ContentReaction')) {
52584
+ this.i18n.registerContent('ContentReaction', CONTENT_REACTION_I18N);
52585
+ }
52586
+ }
52587
+ ngOnInit() {
52588
+ this.loadPreviousReaction();
52589
+ }
52590
+ ngOnChanges(changes) {
52591
+ if (changes['props'] && !changes['props'].firstChange) {
52592
+ this.loadPreviousReaction();
52593
+ }
52594
+ }
52595
+ async loadPreviousReaction() {
52596
+ if (!this.props.entityRef)
52597
+ return;
52598
+ // Si hay initialValue, usarlo directamente sin consultar
52599
+ if (this.props.initialValue) {
52600
+ this.state.update(s => ({
52601
+ ...s,
52602
+ selectedValue: this.props.initialValue,
52603
+ hadPreviousReaction: true,
52604
+ isLoading: false,
52605
+ isSubmitted: true,
52606
+ }));
52607
+ return;
52608
+ }
52609
+ // Si skipInitialCheck está activo, no consultar
52610
+ if (this.props.skipInitialCheck) {
52611
+ return;
52612
+ }
52613
+ this.state.update(s => ({ ...s, isLoading: true, error: null }));
52614
+ try {
52615
+ const check = await this.feedbackService.checkFeedback(this.props.entityRef.entityType, this.props.entityRef.entityId);
52616
+ if (check.hasFeedback && check.reactionValue) {
52617
+ this.state.update(s => ({
52618
+ ...s,
52619
+ selectedValue: check.reactionValue,
52620
+ hadPreviousReaction: true,
52621
+ isLoading: false,
52622
+ isSubmitted: true,
52623
+ }));
52624
+ }
52625
+ else {
52626
+ this.state.update(s => ({ ...s, isLoading: false }));
52627
+ }
52628
+ }
52629
+ catch (error) {
52630
+ console.error('Error loading previous reaction:', error);
52631
+ this.state.update(s => ({ ...s, isLoading: false }));
52632
+ }
52633
+ }
52634
+ selectReaction(value) {
52635
+ if (this.resolvedProps().disabled || this.resolvedProps().readonly)
52636
+ return;
52637
+ const previousValue = this.state().selectedValue;
52638
+ this.state.update(s => ({
52639
+ ...s,
52640
+ selectedValue: value,
52641
+ isSubmitted: false,
52642
+ error: null,
52643
+ }));
52644
+ this.reactionChange.emit({ value, previousValue });
52645
+ }
52646
+ async submitReaction() {
52647
+ const currentState = this.state();
52648
+ const props = this.resolvedProps();
52649
+ if (!currentState.selectedValue || props.disabled)
52650
+ return;
52651
+ this.state.update(s => ({ ...s, isLoading: true, error: null }));
52652
+ try {
52653
+ // Determinar si usar endpoint anónimo o autenticado
52654
+ const isAuthenticated = this.auth?.isAuthenticated() ?? false;
52655
+ const useAnonymous = this.props.allowAnonymous && !isAuthenticated;
52656
+ if (useAnonymous) {
52657
+ // Usar endpoint anónimo (público)
52658
+ await this.feedbackService.createAnonymousReaction(props.entityRef, currentState.selectedValue, currentState.comment || undefined);
52659
+ }
52660
+ else {
52661
+ // Usar endpoint autenticado
52662
+ await this.feedbackService.createReaction(props.entityRef, currentState.selectedValue, currentState.comment || undefined);
52663
+ }
52664
+ this.state.update(s => ({
52665
+ ...s,
52666
+ isLoading: false,
52667
+ isSubmitted: true,
52668
+ comment: '',
52669
+ hadPreviousReaction: !useAnonymous, // Solo para autenticados
52670
+ }));
52671
+ this.reactionSubmit.emit({
52672
+ value: currentState.selectedValue,
52673
+ comment: currentState.comment || undefined,
52674
+ entityRef: props.entityRef,
52675
+ isUpdate: currentState.hadPreviousReaction,
52676
+ });
52677
+ if (props.showThankYou) {
52678
+ this.toast.show({
52679
+ message: props.thankYouMessage,
52680
+ duration: 2000,
52681
+ });
52682
+ }
52683
+ }
52684
+ catch (error) {
52685
+ console.error('Error submitting reaction:', error);
52686
+ this.state.update(s => ({
52687
+ ...s,
52688
+ isLoading: false,
52689
+ error: this.t('errorSubmitting'),
52690
+ }));
52691
+ this.toast.show({
52692
+ message: this.t('errorSubmitting'),
52693
+ duration: 3000,
52694
+ });
52695
+ }
52696
+ }
52697
+ updateComment(event) {
52698
+ const value = event.detail.value || '';
52699
+ this.state.update(s => ({ ...s, comment: value }));
52700
+ }
52701
+ getEmoji(index) {
52702
+ return this.resolvedProps().emojis[index];
52703
+ }
52704
+ getEmojiLabel(index) {
52705
+ return this.resolvedProps().emojiLabels[index];
52706
+ }
52707
+ isSelected(value) {
52708
+ return this.state().selectedValue === value;
52709
+ }
52710
+ t(key) {
52711
+ const translations = {
52712
+ question: '¿Te resultó útil este contenido?',
52713
+ commentPlaceholder: 'Cuéntanos más (opcional)...',
52714
+ submit: 'Enviar opinión',
52715
+ update: 'Actualizar opinión',
52716
+ thankYou: '¡Gracias por tu opinión!',
52717
+ submitted: 'Tu opinión ha sido registrada',
52718
+ errorSubmitting: 'Error al enviar. Intenta de nuevo.',
52719
+ positiveLabel: 'Sí, mucho',
52720
+ negativeLabel: 'No tanto',
52721
+ negativeFeedbackTitle: '¿Cuéntanos qué no te gustó?',
52722
+ };
52723
+ return this.i18n.t(key, 'ContentReaction') || translations[key] || key;
52724
+ }
52725
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ContentReactionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
52726
+ 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\">\n {{ state().selectedValue === 'negative' ? t('negativeFeedbackTitle') : resolvedProps().question }}\n </p>\n\n <!-- Botones de reacci\u00F3n \u2014 variante emoji (caras, estrellas, thumbs, \u2026) -->\n @if (resolvedProps().variant === 'emoji') {\n <div class=\"reaction-buttons reaction-buttons--emoji\">\n @for (value of activeReactionValues(); track value; let i = $index) {\n <ion-button\n fill=\"clear\"\n shape=\"round\"\n [class.selected]=\"isSelected(value)\"\n [attr.aria-label]=\"resolvedProps().emojiLabels[i]\"\n [attr.aria-pressed]=\"isSelected(value)\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction(value)\"\n >\n <span class=\"emoji-btn\">{{ resolvedProps().emojis[i] }}</span>\n </ion-button>\n }\n </div>\n } @else {\n <!-- Botones de reacci\u00F3n \u2014 variante texto (default) -->\n <div class=\"reaction-buttons\">\n <ion-button\n [fill]=\"isSelected('positive') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('positive')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('positive')\"\n >\n {{ t('positiveLabel') }}\n </ion-button>\n <ion-button\n [fill]=\"isSelected('negative') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('negative')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('negative')\"\n >\n {{ t('negativeLabel') }}\n </ion-button>\n </div>\n }\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\" shape=\"round\" [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:1.0625rem;font-weight:700;color:var(--ion-color-dark);margin:0 0 16px}.reaction-buttons{display:flex;justify-content:center;gap:12px;margin-bottom:20px}.reaction-buttons--emoji{gap:8px}.reaction-buttons--emoji ion-button{--padding-start: 4px;--padding-end: 4px;transition:transform .15s ease}.reaction-buttons--emoji ion-button.selected{transform:scale(1.25)}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]){opacity:.55}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]):hover{opacity:1}.emoji-btn{font-size:2rem;line-height:1;display:block}.comment-section{margin-top:16px;animation:slideIn .3s ease-out}.comment-section .comment-textarea{--background: transparent;--border-radius: 8px;--padding-start: 12px;--padding-end: 12px;width:100%}.comment-section .char-count{display:block;text-align:right;font-size:.75rem;color:var(--ion-color-medium);margin-top:4px}.submit-button{margin-top:16px}.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:.875rem}.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"] }] }); }
52727
+ }
52728
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ContentReactionComponent, decorators: [{
52729
+ type: Component,
52730
+ 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\">\n {{ state().selectedValue === 'negative' ? t('negativeFeedbackTitle') : resolvedProps().question }}\n </p>\n\n <!-- Botones de reacci\u00F3n \u2014 variante emoji (caras, estrellas, thumbs, \u2026) -->\n @if (resolvedProps().variant === 'emoji') {\n <div class=\"reaction-buttons reaction-buttons--emoji\">\n @for (value of activeReactionValues(); track value; let i = $index) {\n <ion-button\n fill=\"clear\"\n shape=\"round\"\n [class.selected]=\"isSelected(value)\"\n [attr.aria-label]=\"resolvedProps().emojiLabels[i]\"\n [attr.aria-pressed]=\"isSelected(value)\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction(value)\"\n >\n <span class=\"emoji-btn\">{{ resolvedProps().emojis[i] }}</span>\n </ion-button>\n }\n </div>\n } @else {\n <!-- Botones de reacci\u00F3n \u2014 variante texto (default) -->\n <div class=\"reaction-buttons\">\n <ion-button\n [fill]=\"isSelected('positive') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('positive')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('positive')\"\n >\n {{ t('positiveLabel') }}\n </ion-button>\n <ion-button\n [fill]=\"isSelected('negative') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('negative')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('negative')\"\n >\n {{ t('negativeLabel') }}\n </ion-button>\n </div>\n }\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\" shape=\"round\" [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:1.0625rem;font-weight:700;color:var(--ion-color-dark);margin:0 0 16px}.reaction-buttons{display:flex;justify-content:center;gap:12px;margin-bottom:20px}.reaction-buttons--emoji{gap:8px}.reaction-buttons--emoji ion-button{--padding-start: 4px;--padding-end: 4px;transition:transform .15s ease}.reaction-buttons--emoji ion-button.selected{transform:scale(1.25)}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]){opacity:.55}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]):hover{opacity:1}.emoji-btn{font-size:2rem;line-height:1;display:block}.comment-section{margin-top:16px;animation:slideIn .3s ease-out}.comment-section .comment-textarea{--background: transparent;--border-radius: 8px;--padding-start: 12px;--padding-end: 12px;width:100%}.comment-section .char-count{display:block;text-align:right;font-size:.75rem;color:var(--ion-color-medium);margin-top:4px}.submit-button{margin-top:16px}.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:.875rem}.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"] }]
52731
+ }], ctorParameters: () => [], propDecorators: { props: [{
52732
+ type: Input
52733
+ }], reactionSubmit: [{
52734
+ type: Output
52735
+ }], reactionChange: [{
52736
+ type: Output
52737
+ }] } });
52738
+
52739
+ /**
52740
+ * Defaults i18n (es/en) embebidos en `val-about-view`. Auto-registrados en el
52741
+ * constructor del componente si el consumer no proveyó el namespace
52742
+ * (`Settings.About` por default). SOLO incluye las keys que la vista usa.
52743
+ *
52744
+ * Textos portados EXACTOS del override `Settings.About` de showcase
52745
+ * (`frontend/showcase/src/i18n/index.ts`). Las keys de las acciones
52746
+ * showcase-specific (`feedback*`, `share*`, `faq*`, `donate*`) NO se incluyen:
52747
+ * esas acciones se modelan ahora como `actions[]` de la config (el consumer
52748
+ * aporta sus propios textos). `copyrightText` se incluye aquí (antes salía del
52749
+ * namespace `Layout`) para que la vista sea autocontenida.
52750
+ */
52751
+ const ABOUT_VIEW_I18N = {
52752
+ es: {
52753
+ pageTitle: 'Acerca de',
52754
+ pageDescription: 'Demo del factory Valtech',
52755
+ tagline: 'App de demo — explora las funcionalidades en vivo',
52756
+ descTitle: 'Acerca de Showcase',
52757
+ descBody: 'Showcase es la app de referencia del factory Valtech. Aquí puedes explorar el flujo completo en vivo: autenticación, perfil, organizaciones, notificaciones y ajustes de cuenta.',
52758
+ actionsTitle: 'Acciones',
52759
+ rateTitle: 'Calificar la app',
52760
+ rateDesc: 'Cuéntanos qué te ha parecido la experiencia.',
52761
+ rateModalTitle: 'Tu opinión',
52762
+ rateQuestion: '¿Qué te ha parecido la experiencia hasta ahora?',
52763
+ rateComment: 'Cuéntanos más (opcional)…',
52764
+ rateThanks: '¡Gracias por tu opinión!',
52765
+ rateClose: 'Cerrar',
52766
+ rateBad: 'No tanto',
52767
+ rateMid: 'Bien',
52768
+ rateGood: '¡Me encanta!',
52769
+ socialTitle: 'Síguenos',
52770
+ socialDesc: 'Estamos en redes sociales.',
52771
+ versionLabel: 'Versión',
52772
+ versionCopied: 'v{v} copiado',
52773
+ copyrightText: '© {year}, Soluciones Valtech Ltda. o sus afiliados. Todos los derechos reservados.',
52774
+ },
52775
+ en: {
52776
+ pageTitle: 'About',
52777
+ pageDescription: 'Valtech factory demo',
52778
+ tagline: 'Demo app — explore features live',
52779
+ descTitle: 'About Showcase',
52780
+ descBody: 'Showcase is the reference app for the Valtech factory. Explore the full flow live: authentication, profile, organizations, notifications and account settings.',
52781
+ actionsTitle: 'Actions',
52782
+ rateTitle: 'Rate the app',
52783
+ rateDesc: 'Tell us how your experience has been.',
52784
+ rateModalTitle: 'Your feedback',
52785
+ rateQuestion: 'How has your experience been so far?',
52786
+ rateComment: 'Tell us more (optional)…',
52787
+ rateThanks: 'Thanks for your feedback!',
52788
+ rateClose: 'Close',
52789
+ rateBad: 'Not great',
52790
+ rateMid: 'Good',
52791
+ rateGood: 'Love it!',
52792
+ socialTitle: 'Follow us',
52793
+ socialDesc: "We're on social media.",
52794
+ versionLabel: 'Version',
52795
+ versionCopied: 'v{v} copied',
52796
+ copyrightText: '© {year}, Soluciones Valtech Ltda. or its affiliates. All rights reserved.',
52797
+ },
52798
+ };
52799
+
52800
+ addIcons({
52801
+ copyOutline,
52802
+ starOutline,
52803
+ chatbubbleEllipsesOutline,
52804
+ shareSocialOutline,
52805
+ helpCircleOutline,
52806
+ logoFacebook,
52807
+ logoInstagram,
52808
+ logoLinkedin,
52809
+ logoTwitter,
52810
+ logoYoutube,
52811
+ logoTiktok,
52812
+ });
52813
+ const DEFAULT_NAMESPACE = 'Settings.About';
52814
+ const DEFAULT_BRAND_NAME = 'Valtech';
52815
+ const DEFAULT_LOGO_SRC = 'assets/images/main-icon.png';
52816
+ const DEFAULT_RATING_ENTITY_ID = 'experience';
52817
+ /**
52818
+ * `val-about-view` — vista About full-feature autocontenida (organism).
52819
+ * Promovida desde `showcase` bajo el proceso de ADR-021. Reúne:
52820
+ * - **HERO** — logo + nombre de marca + tagline + pill de versión (copiable).
52821
+ * - **Descripción** — título + cuerpo.
52822
+ * - **Acciones** — action-card de rating built-in (abre `val-content-reaction`
52823
+ * DIRECTAMENTE vía `ModalService`, sin wrapper local — decisión CTO) + las
52824
+ * acciones genéricas que el consumer aporte por `config.actions`.
52825
+ * - **Redes** — links sociales (`config.socialLinks`); sección oculta si vacía.
52826
+ * - **Footer** — copyright con el año actual.
52827
+ *
52828
+ * Lo app-specific (logo, nombre, versión, redes, acciones share/faq) viaja como
52829
+ * DATA de config — NO como slots (ADR-021). La versión la pasa el consumer
52830
+ * (`config.version` = su `APP_VERSION`); la lib NO importa el `version.ts` de la
52831
+ * app. Si no se pasa versión, el pill se oculta.
52832
+ *
52833
+ * NO renderiza ion-content — vive dentro de val-page-wrapper. `ActivatedRoute`
52834
+ * se inyecta `{ optional: true }` solo para leer el route data de config.
52835
+ *
52836
+ * Auto-registra sus defaults i18n (es/en) en el constructor si el consumer no
52837
+ * proveyó el namespace configurado (default `Settings.About`).
52838
+ *
52839
+ * GOTCHA i18n ContentReaction: `val-content-reaction` exige su namespace
52840
+ * `ContentReaction` registrado donde se use (key faltante en binding + CD =
52841
+ * navegador congelado en dev). `ContentReactionComponent` se auto-registra ese
52842
+ * namespace en SU constructor, y como se abre vía `ModalService` ese constructor
52843
+ * corre antes de que su template binde — queda cubierto. Igual lo importamos
52844
+ * como dependencia explícita de esta vista para que la garantía sea local.
52845
+ */
52846
+ class AboutViewComponent {
52847
+ /** Namespace i18n resuelto (capturado para llamadas no-reactivas). */
52848
+ get ns() {
52849
+ return this.resolvedConfig().i18nNamespace;
52850
+ }
52851
+ constructor() {
52852
+ this.nav = inject(NavigationService);
52853
+ this.i18n = inject(I18nService);
52854
+ this.modalService = inject(ModalService);
52855
+ this.toast = inject(ToastService);
52856
+ this.route = inject(ActivatedRoute, { optional: true });
52857
+ this.resolvedConfig = computed(() => {
52858
+ const fromRoute = (this.route?.snapshot.data['aboutConfig'] ?? {});
52859
+ const merged = { ...fromRoute, ...(this.config ?? {}) };
52860
+ return {
52861
+ brand: {
52862
+ name: merged.brand?.name ?? DEFAULT_BRAND_NAME,
52863
+ logoSrc: merged.brand?.logoSrc ?? DEFAULT_LOGO_SRC,
52864
+ },
52865
+ version: merged.version ?? '',
52866
+ socialLinks: merged.socialLinks ?? [],
52867
+ showRating: merged.showRating ?? true,
52868
+ ratingEntityId: merged.ratingEntityId ?? DEFAULT_RATING_ENTITY_ID,
52869
+ actions: merged.actions ?? [],
52870
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE,
52871
+ onRated: merged.onRated,
52872
+ };
52873
+ });
52874
+ this.brandName = computed(() => this.resolvedConfig().brand.name);
52875
+ this.logoSrc = computed(() => this.resolvedConfig().brand.logoSrc);
52876
+ this.version = computed(() => this.resolvedConfig().version);
52877
+ this.socialLinks = computed(() => this.resolvedConfig().socialLinks);
52878
+ this.showRating = computed(() => this.resolvedConfig().showRating);
52879
+ this.actions = computed(() => this.resolvedConfig().actions);
52880
+ /** Copyright del footer — año actual interpolado en `copyrightText`. */
52881
+ this.copyright = computed(() => {
52882
+ this.i18n.lang();
52883
+ const year = String(new Date().getFullYear());
52884
+ return this.tt('copyrightText').replace('{year}', year);
52885
+ });
52886
+ /** Title metadata para secciones — bold + size small. */
52887
+ this.rateActionCard = computed(() => {
52888
+ this.i18n.lang();
52889
+ return {
52890
+ icon: { ionicon: 'star-outline', color: 'primary' },
52891
+ title: this.tt('rateTitle'),
52892
+ description: this.tt('rateDesc'),
52893
+ size: 'medium',
52894
+ bordered: false,
52895
+ shadowed: false,
52896
+ };
52897
+ });
52898
+ const ns = this.ns;
52899
+ if (!this.i18n.hasNamespace(ns)) {
52900
+ this.i18n.registerContent(ns, ABOUT_VIEW_I18N);
52901
+ }
52902
+ this.nav.setBackHeader('pageTitle', ns, { withMenu: true });
52903
+ }
52904
+ /** Helper i18n bound al namespace resuelto. Reactivo vía `lang()`. */
52905
+ tt(key) {
52906
+ this.i18n.lang();
52907
+ return this.i18n.t(key, this.ns);
52908
+ }
52909
+ /** Title metadata para secciones — bold + size small. */
52910
+ sectionTitle(key) {
52911
+ this.i18n.lang();
52912
+ return {
52913
+ content: this.tt(key),
52914
+ size: 'small',
52915
+ color: 'dark',
52916
+ bold: true,
52917
+ };
52918
+ }
52919
+ /** ActionCardMetadata para una acción genérica del consumer. */
52920
+ extraActionCard(action) {
52921
+ return {
52922
+ icon: { ionicon: action.icon ?? 'chatbubble-ellipses-outline', color: 'primary' },
52923
+ title: action.label,
52924
+ description: action.description,
52925
+ size: 'medium',
52926
+ bordered: false,
52927
+ shadowed: false,
52928
+ };
52929
+ }
52930
+ /** Copia la versión al portapapeles + toast (color dark). */
52931
+ async copyVersion() {
52932
+ const v = this.version();
52933
+ if (!v)
52934
+ return;
52935
+ try {
52936
+ await navigator.clipboard.writeText(v);
52937
+ this.toast.show({
52938
+ message: this.tt('versionCopied').replace('{v}', v),
52939
+ color: 'dark',
52940
+ duration: 1500,
52941
+ });
52942
+ }
52943
+ catch {
52944
+ // clipboard no disponible — silencioso
52945
+ }
52946
+ }
52947
+ /** Abre una URL externa en nueva tab. `mailto:` queda en la misma tab. */
52948
+ openUrl(url) {
52949
+ if (url.startsWith('mailto:')) {
52950
+ window.location.href = url;
52951
+ return;
52952
+ }
52953
+ window.open(url, '_blank', 'noopener,noreferrer');
52954
+ }
52955
+ /**
52956
+ * "Calificar la app" — abre `val-content-reaction` DIRECTAMENTE como sheet
52957
+ * modal (sin wrapper local — decisión CTO). El componente persiste la reacción
52958
+ * contra el backend de feedback y muestra su propio estado de "gracias"; el
52959
+ * sheet se cierra arrastrando (breakpoint 0). El namespace i18n
52960
+ * `ContentReaction` lo auto-registra `ContentReactionComponent` en su
52961
+ * constructor, que corre al crear el modal — sin riesgo de key faltante.
52962
+ *
52963
+ * Sheet (bottom sheet): el contenido scrollea dentro y el botón "Enviar" queda
52964
+ * siempre alcanzable, también en viewports chicos.
52965
+ */
52966
+ async openRating() {
52967
+ const reactionProps = {
52968
+ // entityType "app" distingue esta reacción de las de artículos/FAQs.
52969
+ entityRef: { entityType: 'app', entityId: this.resolvedConfig().ratingEntityId },
52970
+ question: this.tt('rateQuestion'),
52971
+ commentPlaceholder: this.tt('rateComment'),
52972
+ thankYouMessage: this.tt('rateThanks'),
52973
+ showComment: true,
52974
+ showThankYou: true,
52975
+ // Modal one-shot: NO bloquear el render con el check de rating previo.
52976
+ skipInitialCheck: true,
52977
+ emojis: ['😕', '🙂', '😍'],
52978
+ emojiLabels: [this.tt('rateBad'), this.tt('rateMid'), this.tt('rateGood')],
52979
+ };
52980
+ await this.modalService.open({
52981
+ component: ContentReactionComponent,
52982
+ componentProps: { props: reactionProps },
52983
+ breakpoints: {
52984
+ initialBreakpoint: 0.9,
52985
+ breakpoints: [0, 0.9, 1],
52986
+ showHandle: true,
52987
+ handleBehavior: 'cycle',
52988
+ },
52989
+ });
52990
+ this.resolvedConfig().onRated?.();
52991
+ }
52992
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AboutViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
52993
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AboutViewComponent, isStandalone: true, selector: "val-about-view", inputs: { config: "config" }, ngImport: i0, template: `
52994
+ <ion-grid class="about-grid">
52995
+ <!-- HERO: logo + name + version -->
52996
+ <div class="about-hero">
52997
+ <img class="about-hero__logo" [src]="logoSrc()" [alt]="brandName()" />
52998
+ <val-title
52999
+ [props]="{
53000
+ content: brandName(),
53001
+ size: 'xlarge',
53002
+ color: 'dark',
53003
+ bold: true,
53004
+ }"
53005
+ />
53006
+ <val-text
53007
+ [props]="{
53008
+ content: tt('tagline'),
53009
+ size: 'medium',
53010
+ color: 'dark',
53011
+ bold: false,
53012
+ }"
53013
+ />
53014
+ @if (version()) {
53015
+ <div
53016
+ class="version-row"
53017
+ (click)="copyVersion()"
53018
+ role="button"
53019
+ [attr.aria-label]="tt('versionLabel') + ' ' + version()"
53020
+ >
53021
+ <val-pill
53022
+ [props]="{
53023
+ label: tt('versionLabel') + ' ' + version(),
53024
+ color: 'dark',
53025
+ weight: 'bold',
53026
+ size: 'small',
53027
+ }"
53028
+ />
53029
+ <ion-icon name="copy-outline" class="version-copy-icon" />
53030
+ </div>
53031
+ }
53032
+ </div>
53033
+
53034
+ <!-- DESCRIPTION -->
53035
+ <section class="about-section">
53036
+ <val-title [props]="sectionTitle('descTitle')" />
53037
+ <val-text
53038
+ [props]="{
53039
+ content: tt('descBody'),
53040
+ size: 'medium',
53041
+ color: 'dark',
53042
+ bold: false,
53043
+ }"
53044
+ />
53045
+ </section>
53046
+
53047
+ <!-- ACTIONS — rating built-in + acciones del consumer. -->
53048
+ @if (showRating() || actions().length > 0) {
53049
+ <section class="about-section about-section--reviews">
53050
+ <val-title [props]="sectionTitle('actionsTitle')" />
53051
+ <div class="about-actions">
53052
+ @if (showRating()) {
53053
+ <val-action-card [props]="rateActionCard()" (onClick)="openRating()" />
53054
+ }
53055
+ @for (action of actions(); track action.label) {
53056
+ <val-action-card [props]="extraActionCard(action)" (onClick)="action.onClick()" />
53057
+ }
53058
+ </div>
53059
+ </section>
53060
+ }
53061
+
53062
+ <!-- SOCIAL — penúltimo, justo antes del footer de derechos. -->
53063
+ @if (socialLinks().length > 0) {
53064
+ <section class="about-section">
53065
+ <val-title [props]="sectionTitle('socialTitle')" />
53066
+ <val-text
53067
+ [props]="{
53068
+ content: tt('socialDesc'),
53069
+ size: 'medium',
53070
+ color: 'dark',
53071
+ bold: false,
53072
+ }"
53073
+ />
53074
+ <div class="about-social">
53075
+ @for (s of socialLinks(); track s.url) {
53076
+ <ion-button
53077
+ fill="clear"
53078
+ size="small"
53079
+ class="social-btn"
53080
+ [attr.aria-label]="s.label"
53081
+ (click)="openUrl(s.url)"
53082
+ >
53083
+ <ion-icon [name]="s.icon" slot="icon-only" />
53084
+ </ion-button>
53085
+ }
53086
+ </div>
53087
+ </section>
53088
+ }
53089
+
53090
+ <!-- FOOTER -->
53091
+ <footer class="about-footer">
53092
+ <val-text
53093
+ [props]="{
53094
+ content: copyright(),
53095
+ size: 'small',
53096
+ color: 'dark',
53097
+ bold: false,
53098
+ }"
53099
+ />
53100
+ </footer>
53101
+ </ion-grid>
53102
+ `, isInline: true, styles: [":host{display:block}.about-grid{padding:16px 0;max-width:720px;margin:0 auto}.about-hero{display:flex;flex-direction:column;align-items:center;gap:8px;padding:24px 0 16px;text-align:center}.about-hero__logo{height:96px;width:auto;display:block}.version-row{display:flex;align-items:center;gap:6px;cursor:pointer;opacity:.85;transition:opacity .15s ease}.version-row:hover{opacity:1}.version-copy-icon{font-size:14px;color:var(--ion-color-medium)}.about-section{padding:16px 0 8px;display:flex;flex-direction:column;gap:8px}.about-actions{display:flex;flex-direction:column;gap:8px}.about-section--reviews val-action-card{display:block}.about-section--reviews val-action-card ::ng-deep .action-card{background:var(--ion-color-light, #f4f5f8);box-shadow:none}.about-section--reviews val-action-card ::ng-deep .action-card__description{color:var(--ion-color-dark);opacity:.7}.about-social{display:flex;flex-wrap:wrap;gap:10px;padding-top:8px}.about-social .social-btn{--border-radius: 50%;--background: var(--ion-color-light, #f4f5f8);--color: var(--ion-color-dark);--padding-start: 0;--padding-end: 0;width:48px;height:48px;margin:0;transition:transform .15s ease}.about-social .social-btn:hover{--background: var(--ion-color-primary-tint, #8a4ce5);--color: #fff;transform:translateY(-2px)}.about-social .social-btn ion-icon{font-size:22px}.about-footer{display:flex;flex-direction:column;align-items:center;gap:4px;padding:24px 0;text-align:center}:host-context(body.dark) val-action-card ::ng-deep .action-card,:host-context(html.ion-palette-dark) val-action-card ::ng-deep .action-card,:host-context([data-theme=\"dark\"]) val-action-card ::ng-deep .action-card{background:#ffffff0a}:host-context(body.dark) .about-section--reviews val-action-card ::ng-deep .action-card:hover,:host-context(html.ion-palette-dark) .about-section--reviews val-action-card ::ng-deep .action-card:hover,:host-context([data-theme=\"dark\"]) .about-section--reviews val-action-card ::ng-deep .action-card:hover{background:#ffffff14}:host-context(body.dark) .about-social .social-btn,:host-context(html.ion-palette-dark) .about-social .social-btn,:host-context([data-theme=\"dark\"]) .about-social .social-btn{--background: rgba(255, 255, 255, .04)}\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: IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: PillComponent, selector: "val-pill", inputs: ["preset", "props"], outputs: ["pillClick", "pillAction"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ActionCardComponent, selector: "val-action-card", inputs: ["props"], outputs: ["onClick"] }] }); }
53103
+ }
53104
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AboutViewComponent, decorators: [{
53105
+ type: Component,
53106
+ args: [{ selector: 'val-about-view', standalone: true, imports: [IonButton, IonGrid, IonIcon, PillComponent, TitleComponent, TextComponent, ActionCardComponent], template: `
53107
+ <ion-grid class="about-grid">
53108
+ <!-- HERO: logo + name + version -->
53109
+ <div class="about-hero">
53110
+ <img class="about-hero__logo" [src]="logoSrc()" [alt]="brandName()" />
53111
+ <val-title
53112
+ [props]="{
53113
+ content: brandName(),
53114
+ size: 'xlarge',
53115
+ color: 'dark',
53116
+ bold: true,
53117
+ }"
53118
+ />
53119
+ <val-text
53120
+ [props]="{
53121
+ content: tt('tagline'),
53122
+ size: 'medium',
53123
+ color: 'dark',
53124
+ bold: false,
53125
+ }"
53126
+ />
53127
+ @if (version()) {
53128
+ <div
53129
+ class="version-row"
53130
+ (click)="copyVersion()"
53131
+ role="button"
53132
+ [attr.aria-label]="tt('versionLabel') + ' ' + version()"
53133
+ >
53134
+ <val-pill
53135
+ [props]="{
53136
+ label: tt('versionLabel') + ' ' + version(),
53137
+ color: 'dark',
53138
+ weight: 'bold',
53139
+ size: 'small',
53140
+ }"
53141
+ />
53142
+ <ion-icon name="copy-outline" class="version-copy-icon" />
53143
+ </div>
53144
+ }
53145
+ </div>
53146
+
53147
+ <!-- DESCRIPTION -->
53148
+ <section class="about-section">
53149
+ <val-title [props]="sectionTitle('descTitle')" />
53150
+ <val-text
53151
+ [props]="{
53152
+ content: tt('descBody'),
53153
+ size: 'medium',
53154
+ color: 'dark',
53155
+ bold: false,
53156
+ }"
53157
+ />
53158
+ </section>
53159
+
53160
+ <!-- ACTIONS — rating built-in + acciones del consumer. -->
53161
+ @if (showRating() || actions().length > 0) {
53162
+ <section class="about-section about-section--reviews">
53163
+ <val-title [props]="sectionTitle('actionsTitle')" />
53164
+ <div class="about-actions">
53165
+ @if (showRating()) {
53166
+ <val-action-card [props]="rateActionCard()" (onClick)="openRating()" />
53167
+ }
53168
+ @for (action of actions(); track action.label) {
53169
+ <val-action-card [props]="extraActionCard(action)" (onClick)="action.onClick()" />
53170
+ }
53171
+ </div>
53172
+ </section>
53173
+ }
53174
+
53175
+ <!-- SOCIAL — penúltimo, justo antes del footer de derechos. -->
53176
+ @if (socialLinks().length > 0) {
53177
+ <section class="about-section">
53178
+ <val-title [props]="sectionTitle('socialTitle')" />
53179
+ <val-text
53180
+ [props]="{
53181
+ content: tt('socialDesc'),
53182
+ size: 'medium',
53183
+ color: 'dark',
53184
+ bold: false,
53185
+ }"
53186
+ />
53187
+ <div class="about-social">
53188
+ @for (s of socialLinks(); track s.url) {
53189
+ <ion-button
53190
+ fill="clear"
53191
+ size="small"
53192
+ class="social-btn"
53193
+ [attr.aria-label]="s.label"
53194
+ (click)="openUrl(s.url)"
53195
+ >
53196
+ <ion-icon [name]="s.icon" slot="icon-only" />
53197
+ </ion-button>
53198
+ }
53199
+ </div>
53200
+ </section>
53201
+ }
53202
+
53203
+ <!-- FOOTER -->
53204
+ <footer class="about-footer">
53205
+ <val-text
53206
+ [props]="{
53207
+ content: copyright(),
53208
+ size: 'small',
53209
+ color: 'dark',
53210
+ bold: false,
53211
+ }"
53212
+ />
53213
+ </footer>
53214
+ </ion-grid>
53215
+ `, styles: [":host{display:block}.about-grid{padding:16px 0;max-width:720px;margin:0 auto}.about-hero{display:flex;flex-direction:column;align-items:center;gap:8px;padding:24px 0 16px;text-align:center}.about-hero__logo{height:96px;width:auto;display:block}.version-row{display:flex;align-items:center;gap:6px;cursor:pointer;opacity:.85;transition:opacity .15s ease}.version-row:hover{opacity:1}.version-copy-icon{font-size:14px;color:var(--ion-color-medium)}.about-section{padding:16px 0 8px;display:flex;flex-direction:column;gap:8px}.about-actions{display:flex;flex-direction:column;gap:8px}.about-section--reviews val-action-card{display:block}.about-section--reviews val-action-card ::ng-deep .action-card{background:var(--ion-color-light, #f4f5f8);box-shadow:none}.about-section--reviews val-action-card ::ng-deep .action-card__description{color:var(--ion-color-dark);opacity:.7}.about-social{display:flex;flex-wrap:wrap;gap:10px;padding-top:8px}.about-social .social-btn{--border-radius: 50%;--background: var(--ion-color-light, #f4f5f8);--color: var(--ion-color-dark);--padding-start: 0;--padding-end: 0;width:48px;height:48px;margin:0;transition:transform .15s ease}.about-social .social-btn:hover{--background: var(--ion-color-primary-tint, #8a4ce5);--color: #fff;transform:translateY(-2px)}.about-social .social-btn ion-icon{font-size:22px}.about-footer{display:flex;flex-direction:column;align-items:center;gap:4px;padding:24px 0;text-align:center}:host-context(body.dark) val-action-card ::ng-deep .action-card,:host-context(html.ion-palette-dark) val-action-card ::ng-deep .action-card,:host-context([data-theme=\"dark\"]) val-action-card ::ng-deep .action-card{background:#ffffff0a}:host-context(body.dark) .about-section--reviews val-action-card ::ng-deep .action-card:hover,:host-context(html.ion-palette-dark) .about-section--reviews val-action-card ::ng-deep .action-card:hover,:host-context([data-theme=\"dark\"]) .about-section--reviews val-action-card ::ng-deep .action-card:hover{background:#ffffff14}:host-context(body.dark) .about-social .social-btn,:host-context(html.ion-palette-dark) .about-social .social-btn,:host-context([data-theme=\"dark\"]) .about-social .social-btn{--background: rgba(255, 255, 255, .04)}\n"] }]
53216
+ }], ctorParameters: () => [], propDecorators: { config: [{
53217
+ type: Input
53218
+ }] } });
53219
+
53220
+ /**
53221
+ * Configuración acotada de `val-about-view` (object-first). La vista About es
53222
+ * app-specific por naturaleza (logo, nombre, versión, redes, acciones extra),
53223
+ * así que lo propio de cada app se parametriza como **DATA de config** — NO como
53224
+ * slots arbitrarios (ADR-021: una vista full-feature no abre slots; si falta
53225
+ * algo, se promueve un nuevo punto de extensión).
53226
+ *
53227
+ * Ejes de variación permitidos:
53228
+ * - **branding** — `brand.name` / `brand.logoSrc` (qué muestra el HERO).
53229
+ * - **versión** — `version` la pasa el consumer (la lib NO puede importar el
53230
+ * `version.ts` de la app). Si no se pasa, se oculta el pill de versión.
53231
+ * - **redes** — `socialLinks`; si está vacío se oculta la sección social.
53232
+ * - **rating** — `showRating` + `ratingEntityId` + `onRated`.
53233
+ * - **acciones extra** — `actions[]` genéricas que el consumer agrega junto a
53234
+ * la acción de rating built-in.
53235
+ * - **branding i18n** — `i18nNamespace` (default `'Settings.About'`).
53236
+ */
53237
+
53238
+ /**
53239
+ * Helper para montar la vista About (`val-about-view`) como ruta en una app del
53240
+ * factory. El `config` se pasa por route `data` (`aboutConfig`) y el componente
53241
+ * lo lee como fallback de su `@Input() config` (el `@Input` gana).
53242
+ *
53243
+ * @example
53244
+ * ```ts
53245
+ * // settings.routes.ts
53246
+ * import { APP_VERSION } from '../version';
53247
+ *
53248
+ * export const settingsRoutes: Routes = [
53249
+ * ...provideValtechAboutRoutes({ config: { version: APP_VERSION } }), // → /app/about
53250
+ * { path: 'preferences', loadComponent: () => ... },
53251
+ * ];
53252
+ *
53253
+ * // con redes y acciones extra:
53254
+ * ...provideValtechAboutRoutes({
53255
+ * config: {
53256
+ * version: APP_VERSION,
53257
+ * socialLinks: VALTECH_SOCIAL_LINKS.map(s => ({ icon: s.icon, url: s.url, label: s.name })),
53258
+ * actions: [{ label: 'Compartir', icon: 'share-social-outline', onClick: () => share() }],
53259
+ * },
53260
+ * }),
53261
+ * ```
53262
+ */
53263
+ function provideValtechAboutRoutes(opts) {
53264
+ return [
53265
+ {
53266
+ path: opts?.path ?? 'about',
53267
+ component: AboutViewComponent,
53268
+ data: { aboutConfig: opts?.config },
53269
+ },
53270
+ ];
53271
+ }
53272
+
52500
53273
  /** Built-in label sets for the three platform locales. */
52501
53274
  const CALLOUT_LABELS = {
52502
53275
  es: {
@@ -58594,263 +59367,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
58594
59367
  type: Output
58595
59368
  }] } });
58596
59369
 
58597
- /**
58598
- * Defaults i18n del componente (es/en). El componente los auto-registra en su
58599
- * constructor si el consumer no proveyó el namespace `ContentReaction`. Esto
58600
- * garantiza que NUNCA haya una key faltante: una key faltante se evalúa en el
58601
- * template por cada change-detection → `console.warn` por CD → con el
58602
- * `val-debug-console` activo (espeja console.* a un signal) se dispara un loop
58603
- * infinito que congela el navegador en dev. Auto-registrar lo elimina de raíz.
58604
- */
58605
- const CONTENT_REACTION_I18N = {
58606
- es: {
58607
- question: '¿Te resultó útil este contenido?',
58608
- commentPlaceholder: 'Cuéntanos más (opcional)...',
58609
- submit: 'Enviar opinión',
58610
- update: 'Actualizar opinión',
58611
- thankYou: '¡Gracias por tu opinión!',
58612
- submitted: 'Tu opinión ha sido registrada',
58613
- errorSubmitting: 'Error al enviar. Intenta de nuevo.',
58614
- positiveLabel: 'Sí, mucho',
58615
- negativeLabel: 'No tanto',
58616
- negativeFeedbackTitle: '¿Cuéntanos qué no te gustó?',
58617
- },
58618
- en: {
58619
- question: 'Was this content helpful?',
58620
- commentPlaceholder: 'Tell us more (optional)...',
58621
- submit: 'Send feedback',
58622
- update: 'Update feedback',
58623
- thankYou: 'Thanks for your feedback!',
58624
- submitted: 'Your feedback has been recorded',
58625
- errorSubmitting: 'Failed to send. Please try again.',
58626
- positiveLabel: 'Yes, a lot',
58627
- negativeLabel: 'Not really',
58628
- negativeFeedbackTitle: "Tell us what you didn't like?",
58629
- },
58630
- };
58631
- /**
58632
- * Componente para reacciones de contenido con emojis.
58633
- *
58634
- * @example
58635
- * ```html
58636
- * <val-content-reaction
58637
- * [props]="{
58638
- * entityRef: { entityType: 'article', entityId: 'art-123' },
58639
- * question: '¿Te fue útil este artículo?'
58640
- * }"
58641
- * (reactionSubmit)="onReactionSubmit($event)"
58642
- * />
58643
- * ```
58644
- */
58645
- class ContentReactionComponent {
58646
- constructor() {
58647
- this.feedbackService = inject(FeedbackService);
58648
- this.toast = inject(ToastService);
58649
- this.i18n = inject(I18nService);
58650
- this.auth = inject(AuthService, { optional: true });
58651
- this.isConfigured = computed(() => this.feedbackService.isConfigured());
58652
- this.props = {};
58653
- this.reactionSubmit = new EventEmitter();
58654
- this.reactionChange = new EventEmitter();
58655
- // Estado reactivo
58656
- this.state = signal({
58657
- selectedValue: null,
58658
- comment: '',
58659
- isLoading: false,
58660
- isSubmitted: false,
58661
- hadPreviousReaction: false,
58662
- error: null,
58663
- });
58664
- // Valores por defecto
58665
- this.defaultEmojis = ['😞', '😐', '😊'];
58666
- this.defaultLabels = ['No me ayudó', 'Regular', 'Muy útil'];
58667
- // Computed properties
58668
- this.resolvedProps = computed(() => ({
58669
- entityRef: this.props.entityRef,
58670
- question: this.props.question || this.t('question'),
58671
- showComment: this.props.showComment ?? true,
58672
- commentOnValues: this.props.commentOnValues,
58673
- commentPlaceholder: this.props.commentPlaceholder || this.t('commentPlaceholder'),
58674
- maxCommentLength: this.props.maxCommentLength ?? 500,
58675
- emojis: this.props.emojis || this.defaultEmojis,
58676
- emojiLabels: this.props.emojiLabels || this.defaultLabels,
58677
- showThankYou: this.props.showThankYou ?? true,
58678
- thankYouMessage: this.props.thankYouMessage || this.t('thankYou'),
58679
- disabled: this.props.disabled ?? false,
58680
- readonly: this.props.readonly ?? false,
58681
- // Variante: 'emoji' cuando se declara explícitamente o cuando se pasan emojis custom.
58682
- variant: this.props.variant ?? (this.props.emojis ? 'emoji' : 'buttons'),
58683
- }));
58684
- this.activeReactionValues = computed(() => this.resolvedProps().emojis.length === 2 ? ['negative', 'positive'] : ['negative', 'neutral', 'positive']);
58685
- this.showCommentField = computed(() => {
58686
- const sel = this.state().selectedValue;
58687
- if (!sel)
58688
- return false;
58689
- const commentOn = this.resolvedProps().commentOnValues;
58690
- if (commentOn !== undefined)
58691
- return commentOn.includes(sel);
58692
- return this.resolvedProps().showComment;
58693
- });
58694
- this.canSubmit = computed(() => this.state().selectedValue !== null && !this.state().isLoading);
58695
- // Auto-registro de defaults i18n (es/en) si el consumer no proveyó el
58696
- // namespace. Garantiza que `t()` nunca caiga en key faltante. Ver
58697
- // CONTENT_REACTION_I18N. Idempotente: solo la 1ª instancia registra.
58698
- if (!this.i18n.hasNamespace('ContentReaction')) {
58699
- this.i18n.registerContent('ContentReaction', CONTENT_REACTION_I18N);
58700
- }
58701
- }
58702
- ngOnInit() {
58703
- this.loadPreviousReaction();
58704
- }
58705
- ngOnChanges(changes) {
58706
- if (changes['props'] && !changes['props'].firstChange) {
58707
- this.loadPreviousReaction();
58708
- }
58709
- }
58710
- async loadPreviousReaction() {
58711
- if (!this.props.entityRef)
58712
- return;
58713
- // Si hay initialValue, usarlo directamente sin consultar
58714
- if (this.props.initialValue) {
58715
- this.state.update(s => ({
58716
- ...s,
58717
- selectedValue: this.props.initialValue,
58718
- hadPreviousReaction: true,
58719
- isLoading: false,
58720
- isSubmitted: true,
58721
- }));
58722
- return;
58723
- }
58724
- // Si skipInitialCheck está activo, no consultar
58725
- if (this.props.skipInitialCheck) {
58726
- return;
58727
- }
58728
- this.state.update(s => ({ ...s, isLoading: true, error: null }));
58729
- try {
58730
- const check = await this.feedbackService.checkFeedback(this.props.entityRef.entityType, this.props.entityRef.entityId);
58731
- if (check.hasFeedback && check.reactionValue) {
58732
- this.state.update(s => ({
58733
- ...s,
58734
- selectedValue: check.reactionValue,
58735
- hadPreviousReaction: true,
58736
- isLoading: false,
58737
- isSubmitted: true,
58738
- }));
58739
- }
58740
- else {
58741
- this.state.update(s => ({ ...s, isLoading: false }));
58742
- }
58743
- }
58744
- catch (error) {
58745
- console.error('Error loading previous reaction:', error);
58746
- this.state.update(s => ({ ...s, isLoading: false }));
58747
- }
58748
- }
58749
- selectReaction(value) {
58750
- if (this.resolvedProps().disabled || this.resolvedProps().readonly)
58751
- return;
58752
- const previousValue = this.state().selectedValue;
58753
- this.state.update(s => ({
58754
- ...s,
58755
- selectedValue: value,
58756
- isSubmitted: false,
58757
- error: null,
58758
- }));
58759
- this.reactionChange.emit({ value, previousValue });
58760
- }
58761
- async submitReaction() {
58762
- const currentState = this.state();
58763
- const props = this.resolvedProps();
58764
- if (!currentState.selectedValue || props.disabled)
58765
- return;
58766
- this.state.update(s => ({ ...s, isLoading: true, error: null }));
58767
- try {
58768
- // Determinar si usar endpoint anónimo o autenticado
58769
- const isAuthenticated = this.auth?.isAuthenticated() ?? false;
58770
- const useAnonymous = this.props.allowAnonymous && !isAuthenticated;
58771
- if (useAnonymous) {
58772
- // Usar endpoint anónimo (público)
58773
- await this.feedbackService.createAnonymousReaction(props.entityRef, currentState.selectedValue, currentState.comment || undefined);
58774
- }
58775
- else {
58776
- // Usar endpoint autenticado
58777
- await this.feedbackService.createReaction(props.entityRef, currentState.selectedValue, currentState.comment || undefined);
58778
- }
58779
- this.state.update(s => ({
58780
- ...s,
58781
- isLoading: false,
58782
- isSubmitted: true,
58783
- comment: '',
58784
- hadPreviousReaction: !useAnonymous, // Solo para autenticados
58785
- }));
58786
- this.reactionSubmit.emit({
58787
- value: currentState.selectedValue,
58788
- comment: currentState.comment || undefined,
58789
- entityRef: props.entityRef,
58790
- isUpdate: currentState.hadPreviousReaction,
58791
- });
58792
- if (props.showThankYou) {
58793
- this.toast.show({
58794
- message: props.thankYouMessage,
58795
- duration: 2000,
58796
- });
58797
- }
58798
- }
58799
- catch (error) {
58800
- console.error('Error submitting reaction:', error);
58801
- this.state.update(s => ({
58802
- ...s,
58803
- isLoading: false,
58804
- error: this.t('errorSubmitting'),
58805
- }));
58806
- this.toast.show({
58807
- message: this.t('errorSubmitting'),
58808
- duration: 3000,
58809
- });
58810
- }
58811
- }
58812
- updateComment(event) {
58813
- const value = event.detail.value || '';
58814
- this.state.update(s => ({ ...s, comment: value }));
58815
- }
58816
- getEmoji(index) {
58817
- return this.resolvedProps().emojis[index];
58818
- }
58819
- getEmojiLabel(index) {
58820
- return this.resolvedProps().emojiLabels[index];
58821
- }
58822
- isSelected(value) {
58823
- return this.state().selectedValue === value;
58824
- }
58825
- t(key) {
58826
- const translations = {
58827
- question: '¿Te resultó útil este contenido?',
58828
- commentPlaceholder: 'Cuéntanos más (opcional)...',
58829
- submit: 'Enviar opinión',
58830
- update: 'Actualizar opinión',
58831
- thankYou: '¡Gracias por tu opinión!',
58832
- submitted: 'Tu opinión ha sido registrada',
58833
- errorSubmitting: 'Error al enviar. Intenta de nuevo.',
58834
- positiveLabel: 'Sí, mucho',
58835
- negativeLabel: 'No tanto',
58836
- negativeFeedbackTitle: '¿Cuéntanos qué no te gustó?',
58837
- };
58838
- return this.i18n.t(key, 'ContentReaction') || translations[key] || key;
58839
- }
58840
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ContentReactionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
58841
- 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\">\n {{ state().selectedValue === 'negative' ? t('negativeFeedbackTitle') : resolvedProps().question }}\n </p>\n\n <!-- Botones de reacci\u00F3n \u2014 variante emoji (caras, estrellas, thumbs, \u2026) -->\n @if (resolvedProps().variant === 'emoji') {\n <div class=\"reaction-buttons reaction-buttons--emoji\">\n @for (value of activeReactionValues(); track value; let i = $index) {\n <ion-button\n fill=\"clear\"\n shape=\"round\"\n [class.selected]=\"isSelected(value)\"\n [attr.aria-label]=\"resolvedProps().emojiLabels[i]\"\n [attr.aria-pressed]=\"isSelected(value)\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction(value)\"\n >\n <span class=\"emoji-btn\">{{ resolvedProps().emojis[i] }}</span>\n </ion-button>\n }\n </div>\n } @else {\n <!-- Botones de reacci\u00F3n \u2014 variante texto (default) -->\n <div class=\"reaction-buttons\">\n <ion-button\n [fill]=\"isSelected('positive') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('positive')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('positive')\"\n >\n {{ t('positiveLabel') }}\n </ion-button>\n <ion-button\n [fill]=\"isSelected('negative') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('negative')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('negative')\"\n >\n {{ t('negativeLabel') }}\n </ion-button>\n </div>\n }\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\" shape=\"round\" [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:1.0625rem;font-weight:700;color:var(--ion-color-dark);margin:0 0 16px}.reaction-buttons{display:flex;justify-content:center;gap:12px;margin-bottom:20px}.reaction-buttons--emoji{gap:8px}.reaction-buttons--emoji ion-button{--padding-start: 4px;--padding-end: 4px;transition:transform .15s ease}.reaction-buttons--emoji ion-button.selected{transform:scale(1.25)}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]){opacity:.55}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]):hover{opacity:1}.emoji-btn{font-size:2rem;line-height:1;display:block}.comment-section{margin-top:16px;animation:slideIn .3s ease-out}.comment-section .comment-textarea{--background: transparent;--border-radius: 8px;--padding-start: 12px;--padding-end: 12px;width:100%}.comment-section .char-count{display:block;text-align:right;font-size:.75rem;color:var(--ion-color-medium);margin-top:4px}.submit-button{margin-top:16px}.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:.875rem}.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"] }] }); }
58842
- }
58843
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ContentReactionComponent, decorators: [{
58844
- type: Component,
58845
- 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\">\n {{ state().selectedValue === 'negative' ? t('negativeFeedbackTitle') : resolvedProps().question }}\n </p>\n\n <!-- Botones de reacci\u00F3n \u2014 variante emoji (caras, estrellas, thumbs, \u2026) -->\n @if (resolvedProps().variant === 'emoji') {\n <div class=\"reaction-buttons reaction-buttons--emoji\">\n @for (value of activeReactionValues(); track value; let i = $index) {\n <ion-button\n fill=\"clear\"\n shape=\"round\"\n [class.selected]=\"isSelected(value)\"\n [attr.aria-label]=\"resolvedProps().emojiLabels[i]\"\n [attr.aria-pressed]=\"isSelected(value)\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction(value)\"\n >\n <span class=\"emoji-btn\">{{ resolvedProps().emojis[i] }}</span>\n </ion-button>\n }\n </div>\n } @else {\n <!-- Botones de reacci\u00F3n \u2014 variante texto (default) -->\n <div class=\"reaction-buttons\">\n <ion-button\n [fill]=\"isSelected('positive') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('positive')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('positive')\"\n >\n {{ t('positiveLabel') }}\n </ion-button>\n <ion-button\n [fill]=\"isSelected('negative') ? 'solid' : 'outline'\"\n shape=\"round\"\n color=\"dark\"\n [attr.aria-pressed]=\"isSelected('negative')\"\n [disabled]=\"resolvedProps().disabled || resolvedProps().readonly\"\n (click)=\"selectReaction('negative')\"\n >\n {{ t('negativeLabel') }}\n </ion-button>\n </div>\n }\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\" shape=\"round\" [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:1.0625rem;font-weight:700;color:var(--ion-color-dark);margin:0 0 16px}.reaction-buttons{display:flex;justify-content:center;gap:12px;margin-bottom:20px}.reaction-buttons--emoji{gap:8px}.reaction-buttons--emoji ion-button{--padding-start: 4px;--padding-end: 4px;transition:transform .15s ease}.reaction-buttons--emoji ion-button.selected{transform:scale(1.25)}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]){opacity:.55}.reaction-buttons--emoji ion-button:not(.selected):not([disabled]):hover{opacity:1}.emoji-btn{font-size:2rem;line-height:1;display:block}.comment-section{margin-top:16px;animation:slideIn .3s ease-out}.comment-section .comment-textarea{--background: transparent;--border-radius: 8px;--padding-start: 12px;--padding-end: 12px;width:100%}.comment-section .char-count{display:block;text-align:right;font-size:.75rem;color:var(--ion-color-medium);margin-top:4px}.submit-button{margin-top:16px}.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:.875rem}.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"] }]
58846
- }], ctorParameters: () => [], propDecorators: { props: [{
58847
- type: Input
58848
- }], reactionSubmit: [{
58849
- type: Output
58850
- }], reactionChange: [{
58851
- type: Output
58852
- }] } });
58853
-
58854
59370
  const DEFAULT_SPLASH_SCREEN_CONFIG = {
58855
59371
  fadeOutDuration: 300,
58856
59372
  };
@@ -62703,5 +63219,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
62703
63219
  * Generated bundle index. Do not edit.
62704
63220
  */
62705
63221
 
62706
- export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_CARD_DEFAULTS, ARTICLE_SPACING, ARTICLE_STRIP_DEFAULTS, AUTH_CTA_DEFAULTS, AVATAR_UPLOAD_DEFAULTS, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CONTENT_CARD_DEFAULTS, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentCardComponent, ContentLoaderComponent, ContentReactionComponent, ContentService, ContentTransformer, CookieBannerComponent, CountdownComponent, CreateOrgModalComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_CANCEL_BUTTON, DEFAULT_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EditOrgModalComponent, EmailInputComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INFO_CARD_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoCardComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, InviteMemberModalComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MemberDetailModalComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationPreferencesViewComponent, NotificationsService, NotificationsViewComponent, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PermissionCatalogService, PermissionsViewComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestService, RightsFooterComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VERSION, ValtechErrorService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAccountRoutes, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationPreferencesRoutes, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPermissionsRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
63222
+ export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_CARD_DEFAULTS, ARTICLE_SPACING, ARTICLE_STRIP_DEFAULTS, AUTH_CTA_DEFAULTS, AVATAR_UPLOAD_DEFAULTS, AboutViewComponent, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CONTENT_CARD_DEFAULTS, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentCardComponent, ContentLoaderComponent, ContentReactionComponent, ContentService, ContentTransformer, CookieBannerComponent, CountdownComponent, CreateOrgModalComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_CANCEL_BUTTON, DEFAULT_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EditOrgModalComponent, EmailInputComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INFO_CARD_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoCardComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, InviteMemberModalComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MemberDetailModalComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationPreferencesViewComponent, NotificationsService, NotificationsViewComponent, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PermissionCatalogService, PermissionsViewComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestService, RightsFooterComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VERSION, ValtechErrorService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAboutRoutes, provideValtechAccountRoutes, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationPreferencesRoutes, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPermissionsRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
62707
63223
  //# sourceMappingURL=valtech-components.mjs.map