@colijnit/product 261.20.1 → 261.20.2

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.
@@ -21,6 +21,8 @@ import { CommonModule, isPlatformBrowser } from '@angular/common';
21
21
  import * as i6 from '@colijnit/corecomponents_v12';
22
22
  import { IconModule, LoaderModule, ScrollContainerModule, InputNumberPickerModule, ButtonModule, ArticleTileModule, TileModule, IconCollapseHandleModule } from '@colijnit/corecomponents_v12';
23
23
  import { CoDocument } from '@colijnit/mainapi/build/model/co-document.bo';
24
+ import * as i6$1 from '@angular/cdk/overlay';
25
+ import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
24
26
  import { ConfiguratorStatisticsEnvironment } from '@colijnit/articleapi/build/model/configurator-statistics-environment';
25
27
  import * as i3 from '@colijnit/sharedcomponents';
26
28
  import { FilesUploadModule } from '@colijnit/sharedcomponents';
@@ -30,8 +32,8 @@ class Version {
30
32
  constructor() {
31
33
  this.name = "@colijnit/product";
32
34
  this.description = "Product detail page project for iOne";
33
- this.symVer = "261.20.1";
34
- this.publishDate = "11-3-2026, 11:34:54";
35
+ this.symVer = "261.20.2";
36
+ this.publishDate = "24-3-2026, 15:30:44";
35
37
  }
36
38
  }
37
39
 
@@ -957,11 +959,13 @@ class ImageCarouselComponent {
957
959
  this._currentIndex = value;
958
960
  this._scrollCarouselToIndex();
959
961
  }
960
- constructor(_ione, _appEventService, _changeDetector, _domSanitizer) {
962
+ constructor(iconCacheService, _ione, _appEventService, _changeDetector, _domSanitizer) {
963
+ this.iconCacheService = iconCacheService;
961
964
  this._ione = _ione;
962
965
  this._appEventService = _appEventService;
963
966
  this._changeDetector = _changeDetector;
964
967
  this._domSanitizer = _domSanitizer;
968
+ this.icons = IconEnum;
965
969
  this.isPopupOpen = false;
966
970
  this.showRefresh = false;
967
971
  this.resizing = false;
@@ -1059,51 +1063,63 @@ class ImageCarouselComponent {
1059
1063
  const ctx = resizeCanvas.getContext('2d');
1060
1064
  const img = document.createElement('img');
1061
1065
  img.crossOrigin = 'anonymous';
1066
+ const handleFallback = () => {
1067
+ // When a CDN does not accept our tainted canvas, we fallback to the original source.
1068
+ imageViewModel.source = this._domSanitizer.bypassSecurityTrustUrl(source);
1069
+ imageViewModel.originalSource = source;
1070
+ this._changeDetector.detectChanges();
1071
+ };
1072
+ img.onerror = () => {
1073
+ handleFallback();
1074
+ };
1062
1075
  img.onload = () => {
1063
- ctx.imageSmoothingEnabled = true;
1064
- ctx.imageSmoothingQuality = 'high';
1065
- const ow = img.width;
1066
- const oh = img.height;
1067
- const aspect = ow / oh;
1068
- let newW = this._resizeCanvasHeight;
1069
- let newH = this._resizeCanvasHeight;
1070
- if (ow > oh) {
1071
- newH = this._resizeCanvasHeight / aspect;
1076
+ try {
1077
+ ctx.imageSmoothingEnabled = true;
1078
+ ctx.imageSmoothingQuality = 'high';
1079
+ const aspect = img.width / img.height;
1080
+ let newW = this._resizeCanvasHeight;
1081
+ let newH = this._resizeCanvasHeight;
1082
+ if (img.width > img.height) {
1083
+ newH = this._resizeCanvasHeight / aspect;
1084
+ }
1085
+ else {
1086
+ newW = this._resizeCanvasHeight * aspect;
1087
+ }
1088
+ resizeCanvas.width = Math.round(newW);
1089
+ resizeCanvas.height = Math.round(newH);
1090
+ ctx.clearRect(0, 0, resizeCanvas.width, resizeCanvas.height);
1091
+ ctx.drawImage(img, 0, 0, resizeCanvas.width, resizeCanvas.height);
1092
+ const mime = this._handleMimeTypes(source);
1093
+ const resizedSource = resizeCanvas.toDataURL(mime);
1094
+ imageViewModel.source = this._domSanitizer.bypassSecurityTrustUrl(resizedSource);
1095
+ imageViewModel.originalSource = source;
1096
+ this._changeDetector.detectChanges();
1072
1097
  }
1073
- else {
1074
- newW = this._resizeCanvasHeight * aspect;
1098
+ catch (error) {
1099
+ handleFallback();
1075
1100
  }
1076
- resizeCanvas.width = Math.round(newW);
1077
- resizeCanvas.height = Math.round(newH);
1078
- // Ensure transparent background before drawing
1079
- ctx.clearRect(0, 0, resizeCanvas.width, resizeCanvas.height);
1080
- ctx.drawImage(img, 0, 0, resizeCanvas.width, resizeCanvas.height);
1081
- const mime = this._detectPreferredMime(source);
1082
- const resizedSource = mime === 'image/jpeg'
1083
- ? resizeCanvas.toDataURL('image/jpeg', 0.92) // only if original was JPEG
1084
- : resizeCanvas.toDataURL(mime); // PNG/WebP keep alpha
1085
- imageViewModel.source = this._domSanitizer.bypassSecurityTrustUrl(resizedSource);
1086
- imageViewModel.originalSource = source;
1087
- this._changeDetector.detectChanges();
1088
1101
  };
1089
1102
  img.src = source;
1090
1103
  }
1091
- _detectPreferredMime(source) {
1092
- // Data URI check
1093
- const m = source.match(/^data:(image\/[a-zA-Z+.-]+);base64,/);
1094
- if (m) {
1095
- const t = m[1].toLowerCase();
1096
- if (t === 'image/png' || t === 'image/webp' || t === 'image/jpeg')
1097
- return t;
1104
+ _handleMimeTypes(imageSource) {
1105
+ // When we got base64 data
1106
+ if (imageSource.startsWith('data:')) {
1107
+ const match = imageSource.match(/^data:(image\/(png|webp|jpeg|jpg))/i);
1108
+ return match ? match[1].toLowerCase() : 'image/png';
1109
+ }
1110
+ // when we got an URL.
1111
+ const extension = imageSource.substring(imageSource.lastIndexOf('.') + 1).toLowerCase();
1112
+ switch (extension) {
1113
+ case 'png':
1114
+ return 'image/png';
1115
+ case 'webp':
1116
+ return 'image/webp';
1117
+ case 'jpg':
1118
+ case 'jpeg':
1119
+ return 'image/jpeg';
1120
+ default:
1121
+ return 'image/png';
1098
1122
  }
1099
- const lower = source.toLowerCase();
1100
- if (lower.endsWith('.png'))
1101
- return 'image/png';
1102
- if (lower.endsWith('.webp'))
1103
- return 'image/webp';
1104
- if (lower.endsWith('.jpg') || lower.endsWith('.jpeg'))
1105
- return 'image/jpeg';
1106
- return 'image/png';
1107
1123
  }
1108
1124
  _scrollCarouselToIndex() {
1109
1125
  if (this.currentIndex > -1 && this.currentIndex <= this.images.length) {
@@ -1117,147 +1133,137 @@ class ImageCarouselComponent {
1117
1133
  }
1118
1134
  }
1119
1135
  }
1120
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ImageCarouselComponent, deps: [{ token: ProductConnectorService }, { token: ProductEventService }, { token: i0.ChangeDetectorRef }, { token: i1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component }); }
1136
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ImageCarouselComponent, deps: [{ token: IconCacheService }, { token: ProductConnectorService }, { token: ProductEventService }, { token: i0.ChangeDetectorRef }, { token: i1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component }); }
1121
1137
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: ImageCarouselComponent, isStandalone: false, selector: "app-image-carousel", inputs: { showRefresh: "showRefresh", images: "images" }, host: { listeners: { "document:keydown.escape": "onEsc()", "window:resize": "handleWindowResize()", "swipeleft": "gotoNextSlide()", "swiperight": "gotoPrevSlide()" }, properties: { "class.resizing": "this.resizing" } }, viewQueries: [{ propertyName: "carousel", first: true, predicate: ["carousel"], descendants: true, read: ElementRef }], ngImport: i0, template: `
1122
- <div id="product_page_carousel">
1123
- <div id="product_page_carousel_items">
1124
- @if (showLoader) {
1125
- <co-loader></co-loader>
1138
+ <div id="product_page_carousel">
1139
+ <div id="product_page_carousel_items">
1140
+ @if (showLoader) {
1141
+ <co-loader></co-loader>
1142
+ }
1143
+ <div #carousel class="inner-carousel">
1144
+ <!-- This has been taken out of the for loop to prevent flashing images when updating. -->
1145
+ @if (imageViewModels[0]) {
1146
+ <div class="carousel-item" [id]="'slide-0'" [class.active]="isCurrentIndex(0)" (click)="handleShowImage(imageViewModels[0])">
1147
+ <img [src]="imageViewModels[0].source">
1148
+ </div>
1149
+ }
1150
+ @for (imageViewModel of imageViewModels.slice(1); track imageViewModel; let index = $index) {
1151
+ <div class="carousel-item"
1152
+ [id]="'slide-' + (index + 1)" [class.active]="isCurrentIndex((index + 1))" (click)="handleShowImage(imageViewModel)">
1153
+ <img [src]="imageViewModel.source">
1154
+ </div>
1126
1155
  }
1127
- <div #carousel class="inner-carousel">
1128
- <!-- This has been taken out of the for loop to prevent flashing images when updating. -->
1129
- @if (imageViewModels[0]) {
1130
- <div class="carousel-item" [id]="'slide-0'" [class.active]="isCurrentIndex(0)" (click)="handleShowImage(imageViewModels[0])">
1131
- <img [src]="imageViewModels[0].source">
1132
- </div>
1133
- }
1134
- @for (imageViewModel of imageViewModels.slice(1); track imageViewModel; let index = $index) {
1135
- <div class="carousel-item"
1136
- [id]="'slide-' + (index + 1)" [class.active]="isCurrentIndex((index + 1))" (click)="handleShowImage(imageViewModel)">
1137
- <img [src]="imageViewModel.source">
1138
- </div>
1139
- }
1140
- @if (imageViewModels && imageViewModels.length > 1) {
1141
- <div class="carousel-scroller-layer">
1142
- @if (currentIndex > 0) {
1143
- <div class="carousel-item-scroller prev" (click)="gotoPrevSlide()"></div>
1144
- }
1145
- @if (currentIndex < images.length - 1) {
1146
- <div class="carousel-item-scroller next" (click)="gotoNextSlide()"></div>
1147
- }
1148
- </div>
1149
- }
1150
- </div>
1151
- </div>
1152
-
1153
- <div id="product_page_carousel_thumbs">
1154
1156
  @if (imageViewModels && imageViewModels.length > 1) {
1155
- <co-scroll-container class="scroll-container">
1156
- @for (imageViewModel of imageViewModels; track imageViewModel; let index = $index) {
1157
- <div class="carousel-thumb"
1158
- [class.active]="index === currentIndex">
1159
- <img [src]="imageViewModel.source" (click)="handleThumbClick(index)"/>
1160
- </div>
1157
+ <div class="carousel-scroller-layer">
1158
+ @if (currentIndex > 0) {
1159
+ <div class="carousel-item-scroller prev" (click)="gotoPrevSlide()"></div>
1161
1160
  }
1162
- </co-scroll-container>
1161
+ @if (currentIndex < images.length - 1) {
1162
+ <div class="carousel-item-scroller next" (click)="gotoNextSlide()"></div>
1163
+ }
1164
+ </div>
1163
1165
  }
1164
1166
  </div>
1165
1167
  </div>
1166
- <!-- Modal (real popup) -->
1167
- @if (isPopupOpen) {
1168
- <div
1169
- class="image-modal"
1170
- (click)="closePopup()"
1171
- role="dialog"
1172
- aria-modal="true"
1173
- aria-label="Image preview"
1174
- >
1175
- <div class="image-modal__content" (click)="$event.stopPropagation()">
1176
- <button
1177
- class="image-modal__close"
1178
- type="button"
1179
- aria-label="Close"
1180
- (click)="closePopup()"
1181
- >
1182
- ×
1183
- </button>
1184
- <img [src]="selectedImage?.originalSource" alt="Image preview" />
1185
- </div>
1168
+
1169
+ <div id="product_page_carousel_thumbs">
1170
+ @if (imageViewModels && imageViewModels.length > 1) {
1171
+ <co-scroll-container class="scroll-container">
1172
+ @for (imageViewModel of imageViewModels; track imageViewModel; let index = $index) {
1173
+ <div class="carousel-thumb"
1174
+ [class.active]="index === currentIndex">
1175
+ <img [src]="imageViewModel.source" (click)="handleThumbClick(index)"/>
1176
+ </div>
1177
+ }
1178
+ </co-scroll-container>
1179
+ }
1180
+ </div>
1181
+ </div>
1182
+
1183
+ <div cdkOverlayOrigin #trigger="cdkOverlayOrigin"></div>
1184
+ <ng-template
1185
+ cdkConnectedOverlay
1186
+ [cdkConnectedOverlayOrigin]="trigger"
1187
+ [cdkConnectedOverlayOpen]="isPopupOpen"
1188
+ [cdkConnectedOverlayHasBackdrop]="true"
1189
+ (backdropClick)="closePopup()">
1190
+ <div class="image-modal" role="dialog" aria-modal="true" aria-label="Image preview">
1191
+ <div class="image-modal__content">
1192
+ <button class="image-modal__close" type="button" aria-label="Close" (click)="closePopup()">
1193
+ <co-icon [iconData]="iconCacheService.getIcon(icons.CrossSkinny)"></co-icon>
1194
+ </button>
1195
+ <img [src]="selectedImage?.originalSource" alt="Image preview"/>
1186
1196
  </div>
1187
- }
1188
- `, isInline: true, styles: [":host{max-height:540px;height:100%;position:relative}:host:not(.resizing) .inner-carousel{scroll-behavior:smooth;-webkit-overflow-scrolling:touch;scroll-snap-type:x mandatory}#product_page_carousel{position:relative}#product_page_carousel .refresh-button{position:absolute;bottom:10px;right:10px;background:#fff}#product_page_carousel .refresh-button.loading{animation:spin 1s linear infinite}#product_page_carousel .refresh-button:hover{box-shadow:none;background:#74b77f;transition:all .2s ease-in-out}#product_page_carousel .refresh-button:hover ::ng-deep svg path{fill:#fff!important}#product_page_carousel #product_page_carousel_items{position:relative;margin-bottom:10px}#product_page_carousel #product_page_carousel_items ::ng-deep co-loader{position:absolute}#product_page_carousel .inner-carousel{display:flex;flex-direction:row;align-items:center;overflow:hidden;max-height:500px;border:1px solid #efefef}#product_page_carousel .carousel-item{max-height:500px;width:100%;display:flex;cursor:zoom-in;flex-shrink:0;flex-grow:0}#product_page_carousel .carousel-item img{width:100%;height:auto;object-fit:contain}#product_page_carousel .carousel-scroller-layer{height:100%;width:100%;position:absolute;pointer-events:none;top:0;left:0}#product_page_carousel #product_page_carousel_thumbs{display:flex;justify-content:flex-start;height:80px;margin-left:auto;margin-right:auto}#product_page_carousel #product_page_carousel_thumbs ::ng-deep co-scroll-container{padding:0 22px}#product_page_carousel #product_page_carousel_thumbs ::ng-deep co-scroll-container .content-wrapper{padding:0}#product_page_carousel #product_page_carousel_thumbs .carousel-thumb{opacity:1;cursor:pointer;transition:all .2s ease;padding:4px;border:1px solid #f6f5f4}#product_page_carousel #product_page_carousel_thumbs .carousel-thumb.active,#product_page_carousel #product_page_carousel_thumbs .carousel-thumb:hover{border-color:#22313c}#product_page_carousel #product_page_carousel_thumbs .carousel-thumb:not(:last-child){margin-right:10px}#product_page_carousel #product_page_carousel_thumbs .carousel-thumb img{height:68px}.image-modal{position:fixed;inset:0;background:#000c;display:flex;align-items:center;justify-content:center;z-index:1000}.image-modal__content{position:relative;max-width:90vw;max-height:90vh}.image-modal__content img{max-width:90vw;max-height:90vh;object-fit:contain;display:block}.image-modal__close{position:fixed;top:15px;right:30px;background:transparent;border:none;color:#fff;font-size:60px;line-height:1;cursor:pointer}@media screen and (max-width: 650px){#product_page_carousel_thumbs{height:57px!important}#product_page_carousel_thumbs .carousel-thumb img{height:50px!important}}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "component", type: i6.LoaderComponent, selector: "co-loader" }, { kind: "component", type: i6.ScrollContainerComponent, selector: "co-scroll-container", inputs: ["vertical"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1197
+ </div>
1198
+ </ng-template>
1199
+ `, isInline: true, styles: [":host{max-height:540px;height:100%;position:relative}:host:not(.resizing) .inner-carousel{scroll-behavior:smooth;-webkit-overflow-scrolling:touch;scroll-snap-type:x mandatory}#product_page_carousel{position:relative}#product_page_carousel .refresh-button{position:absolute;bottom:10px;right:10px;background:#fff}#product_page_carousel .refresh-button.loading{animation:spin 1s linear infinite}#product_page_carousel .refresh-button:hover{box-shadow:none;background:#74b77f;transition:all .2s ease-in-out}#product_page_carousel .refresh-button:hover ::ng-deep svg path{fill:#fff!important}#product_page_carousel #product_page_carousel_items{position:relative;margin-bottom:10px}#product_page_carousel #product_page_carousel_items ::ng-deep co-loader{position:absolute}#product_page_carousel .inner-carousel{display:flex;flex-direction:row;align-items:center;overflow:hidden;max-height:500px;border:1px solid #efefef}#product_page_carousel .carousel-item{max-height:500px;width:100%;display:flex;cursor:zoom-in;flex-shrink:0;flex-grow:0}#product_page_carousel .carousel-item img{width:100%;height:auto;object-fit:contain}#product_page_carousel .carousel-scroller-layer{height:100%;width:100%;position:absolute;pointer-events:none;top:0;left:0}#product_page_carousel #product_page_carousel_thumbs{display:flex;justify-content:flex-start;height:80px;margin-left:auto;margin-right:auto}#product_page_carousel #product_page_carousel_thumbs ::ng-deep co-scroll-container{padding:0 22px}#product_page_carousel #product_page_carousel_thumbs ::ng-deep co-scroll-container .content-wrapper{padding:0}#product_page_carousel #product_page_carousel_thumbs .carousel-thumb{opacity:1;cursor:pointer;transition:all .2s ease;padding:4px;border:1px solid #f6f5f4}#product_page_carousel #product_page_carousel_thumbs .carousel-thumb.active,#product_page_carousel #product_page_carousel_thumbs .carousel-thumb:hover{border-color:#22313c}#product_page_carousel #product_page_carousel_thumbs .carousel-thumb:not(:last-child){margin-right:10px}#product_page_carousel #product_page_carousel_thumbs .carousel-thumb img{height:68px}.image-modal{position:fixed;inset:0;background:#000c;display:flex;align-items:center;justify-content:center;z-index:1000}.image-modal__content{position:relative;max-width:90vw;max-height:90vh}.image-modal__content img{max-width:90vw;max-height:90vh;object-fit:contain;display:block}.image-modal__close{position:fixed;top:15px;right:30px;background:transparent;border:none;color:#fff;font-size:60px;line-height:1;cursor:pointer}@media screen and (max-width: 650px){#product_page_carousel_thumbs{height:57px!important}#product_page_carousel_thumbs .carousel-thumb img{height:50px!important}}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "component", type: i6.LoaderComponent, selector: "co-loader" }, { kind: "component", type: i6.ScrollContainerComponent, selector: "co-scroll-container", inputs: ["vertical"] }, { kind: "directive", type: i6$1.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i6$1.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "component", type: i6.IconComponent, selector: "co-icon", inputs: ["icon", "iconData"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1189
1200
  }
1190
1201
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ImageCarouselComponent, decorators: [{
1191
1202
  type: Component,
1192
1203
  args: [{ selector: 'app-image-carousel', template: `
1193
- <div id="product_page_carousel">
1194
- <div id="product_page_carousel_items">
1195
- @if (showLoader) {
1196
- <co-loader></co-loader>
1204
+ <div id="product_page_carousel">
1205
+ <div id="product_page_carousel_items">
1206
+ @if (showLoader) {
1207
+ <co-loader></co-loader>
1208
+ }
1209
+ <div #carousel class="inner-carousel">
1210
+ <!-- This has been taken out of the for loop to prevent flashing images when updating. -->
1211
+ @if (imageViewModels[0]) {
1212
+ <div class="carousel-item" [id]="'slide-0'" [class.active]="isCurrentIndex(0)" (click)="handleShowImage(imageViewModels[0])">
1213
+ <img [src]="imageViewModels[0].source">
1214
+ </div>
1215
+ }
1216
+ @for (imageViewModel of imageViewModels.slice(1); track imageViewModel; let index = $index) {
1217
+ <div class="carousel-item"
1218
+ [id]="'slide-' + (index + 1)" [class.active]="isCurrentIndex((index + 1))" (click)="handleShowImage(imageViewModel)">
1219
+ <img [src]="imageViewModel.source">
1220
+ </div>
1197
1221
  }
1198
- <div #carousel class="inner-carousel">
1199
- <!-- This has been taken out of the for loop to prevent flashing images when updating. -->
1200
- @if (imageViewModels[0]) {
1201
- <div class="carousel-item" [id]="'slide-0'" [class.active]="isCurrentIndex(0)" (click)="handleShowImage(imageViewModels[0])">
1202
- <img [src]="imageViewModels[0].source">
1203
- </div>
1204
- }
1205
- @for (imageViewModel of imageViewModels.slice(1); track imageViewModel; let index = $index) {
1206
- <div class="carousel-item"
1207
- [id]="'slide-' + (index + 1)" [class.active]="isCurrentIndex((index + 1))" (click)="handleShowImage(imageViewModel)">
1208
- <img [src]="imageViewModel.source">
1209
- </div>
1210
- }
1211
- @if (imageViewModels && imageViewModels.length > 1) {
1212
- <div class="carousel-scroller-layer">
1213
- @if (currentIndex > 0) {
1214
- <div class="carousel-item-scroller prev" (click)="gotoPrevSlide()"></div>
1215
- }
1216
- @if (currentIndex < images.length - 1) {
1217
- <div class="carousel-item-scroller next" (click)="gotoNextSlide()"></div>
1218
- }
1219
- </div>
1220
- }
1221
- </div>
1222
- </div>
1223
-
1224
- <div id="product_page_carousel_thumbs">
1225
1222
  @if (imageViewModels && imageViewModels.length > 1) {
1226
- <co-scroll-container class="scroll-container">
1227
- @for (imageViewModel of imageViewModels; track imageViewModel; let index = $index) {
1228
- <div class="carousel-thumb"
1229
- [class.active]="index === currentIndex">
1230
- <img [src]="imageViewModel.source" (click)="handleThumbClick(index)"/>
1231
- </div>
1223
+ <div class="carousel-scroller-layer">
1224
+ @if (currentIndex > 0) {
1225
+ <div class="carousel-item-scroller prev" (click)="gotoPrevSlide()"></div>
1232
1226
  }
1233
- </co-scroll-container>
1227
+ @if (currentIndex < images.length - 1) {
1228
+ <div class="carousel-item-scroller next" (click)="gotoNextSlide()"></div>
1229
+ }
1230
+ </div>
1234
1231
  }
1235
1232
  </div>
1236
1233
  </div>
1237
- <!-- Modal (real popup) -->
1238
- @if (isPopupOpen) {
1239
- <div
1240
- class="image-modal"
1241
- (click)="closePopup()"
1242
- role="dialog"
1243
- aria-modal="true"
1244
- aria-label="Image preview"
1245
- >
1246
- <div class="image-modal__content" (click)="$event.stopPropagation()">
1247
- <button
1248
- class="image-modal__close"
1249
- type="button"
1250
- aria-label="Close"
1251
- (click)="closePopup()"
1252
- >
1253
- ×
1254
- </button>
1255
- <img [src]="selectedImage?.originalSource" alt="Image preview" />
1256
- </div>
1234
+
1235
+ <div id="product_page_carousel_thumbs">
1236
+ @if (imageViewModels && imageViewModels.length > 1) {
1237
+ <co-scroll-container class="scroll-container">
1238
+ @for (imageViewModel of imageViewModels; track imageViewModel; let index = $index) {
1239
+ <div class="carousel-thumb"
1240
+ [class.active]="index === currentIndex">
1241
+ <img [src]="imageViewModel.source" (click)="handleThumbClick(index)"/>
1242
+ </div>
1243
+ }
1244
+ </co-scroll-container>
1245
+ }
1246
+ </div>
1247
+ </div>
1248
+
1249
+ <div cdkOverlayOrigin #trigger="cdkOverlayOrigin"></div>
1250
+ <ng-template
1251
+ cdkConnectedOverlay
1252
+ [cdkConnectedOverlayOrigin]="trigger"
1253
+ [cdkConnectedOverlayOpen]="isPopupOpen"
1254
+ [cdkConnectedOverlayHasBackdrop]="true"
1255
+ (backdropClick)="closePopup()">
1256
+ <div class="image-modal" role="dialog" aria-modal="true" aria-label="Image preview">
1257
+ <div class="image-modal__content">
1258
+ <button class="image-modal__close" type="button" aria-label="Close" (click)="closePopup()">
1259
+ <co-icon [iconData]="iconCacheService.getIcon(icons.CrossSkinny)"></co-icon>
1260
+ </button>
1261
+ <img [src]="selectedImage?.originalSource" alt="Image preview"/>
1257
1262
  </div>
1258
- }
1259
- `, changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, styles: [":host{max-height:540px;height:100%;position:relative}:host:not(.resizing) .inner-carousel{scroll-behavior:smooth;-webkit-overflow-scrolling:touch;scroll-snap-type:x mandatory}#product_page_carousel{position:relative}#product_page_carousel .refresh-button{position:absolute;bottom:10px;right:10px;background:#fff}#product_page_carousel .refresh-button.loading{animation:spin 1s linear infinite}#product_page_carousel .refresh-button:hover{box-shadow:none;background:#74b77f;transition:all .2s ease-in-out}#product_page_carousel .refresh-button:hover ::ng-deep svg path{fill:#fff!important}#product_page_carousel #product_page_carousel_items{position:relative;margin-bottom:10px}#product_page_carousel #product_page_carousel_items ::ng-deep co-loader{position:absolute}#product_page_carousel .inner-carousel{display:flex;flex-direction:row;align-items:center;overflow:hidden;max-height:500px;border:1px solid #efefef}#product_page_carousel .carousel-item{max-height:500px;width:100%;display:flex;cursor:zoom-in;flex-shrink:0;flex-grow:0}#product_page_carousel .carousel-item img{width:100%;height:auto;object-fit:contain}#product_page_carousel .carousel-scroller-layer{height:100%;width:100%;position:absolute;pointer-events:none;top:0;left:0}#product_page_carousel #product_page_carousel_thumbs{display:flex;justify-content:flex-start;height:80px;margin-left:auto;margin-right:auto}#product_page_carousel #product_page_carousel_thumbs ::ng-deep co-scroll-container{padding:0 22px}#product_page_carousel #product_page_carousel_thumbs ::ng-deep co-scroll-container .content-wrapper{padding:0}#product_page_carousel #product_page_carousel_thumbs .carousel-thumb{opacity:1;cursor:pointer;transition:all .2s ease;padding:4px;border:1px solid #f6f5f4}#product_page_carousel #product_page_carousel_thumbs .carousel-thumb.active,#product_page_carousel #product_page_carousel_thumbs .carousel-thumb:hover{border-color:#22313c}#product_page_carousel #product_page_carousel_thumbs .carousel-thumb:not(:last-child){margin-right:10px}#product_page_carousel #product_page_carousel_thumbs .carousel-thumb img{height:68px}.image-modal{position:fixed;inset:0;background:#000c;display:flex;align-items:center;justify-content:center;z-index:1000}.image-modal__content{position:relative;max-width:90vw;max-height:90vh}.image-modal__content img{max-width:90vw;max-height:90vh;object-fit:contain;display:block}.image-modal__close{position:fixed;top:15px;right:30px;background:transparent;border:none;color:#fff;font-size:60px;line-height:1;cursor:pointer}@media screen and (max-width: 650px){#product_page_carousel_thumbs{height:57px!important}#product_page_carousel_thumbs .carousel-thumb img{height:50px!important}}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
1260
- }], ctorParameters: () => [{ type: ProductConnectorService }, { type: ProductEventService }, { type: i0.ChangeDetectorRef }, { type: i1.DomSanitizer }], propDecorators: { carousel: [{
1263
+ </div>
1264
+ </ng-template>
1265
+ `, changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, styles: [":host{max-height:540px;height:100%;position:relative}:host:not(.resizing) .inner-carousel{scroll-behavior:smooth;-webkit-overflow-scrolling:touch;scroll-snap-type:x mandatory}#product_page_carousel{position:relative}#product_page_carousel .refresh-button{position:absolute;bottom:10px;right:10px;background:#fff}#product_page_carousel .refresh-button.loading{animation:spin 1s linear infinite}#product_page_carousel .refresh-button:hover{box-shadow:none;background:#74b77f;transition:all .2s ease-in-out}#product_page_carousel .refresh-button:hover ::ng-deep svg path{fill:#fff!important}#product_page_carousel #product_page_carousel_items{position:relative;margin-bottom:10px}#product_page_carousel #product_page_carousel_items ::ng-deep co-loader{position:absolute}#product_page_carousel .inner-carousel{display:flex;flex-direction:row;align-items:center;overflow:hidden;max-height:500px;border:1px solid #efefef}#product_page_carousel .carousel-item{max-height:500px;width:100%;display:flex;cursor:zoom-in;flex-shrink:0;flex-grow:0}#product_page_carousel .carousel-item img{width:100%;height:auto;object-fit:contain}#product_page_carousel .carousel-scroller-layer{height:100%;width:100%;position:absolute;pointer-events:none;top:0;left:0}#product_page_carousel #product_page_carousel_thumbs{display:flex;justify-content:flex-start;height:80px;margin-left:auto;margin-right:auto}#product_page_carousel #product_page_carousel_thumbs ::ng-deep co-scroll-container{padding:0 22px}#product_page_carousel #product_page_carousel_thumbs ::ng-deep co-scroll-container .content-wrapper{padding:0}#product_page_carousel #product_page_carousel_thumbs .carousel-thumb{opacity:1;cursor:pointer;transition:all .2s ease;padding:4px;border:1px solid #f6f5f4}#product_page_carousel #product_page_carousel_thumbs .carousel-thumb.active,#product_page_carousel #product_page_carousel_thumbs .carousel-thumb:hover{border-color:#22313c}#product_page_carousel #product_page_carousel_thumbs .carousel-thumb:not(:last-child){margin-right:10px}#product_page_carousel #product_page_carousel_thumbs .carousel-thumb img{height:68px}.image-modal{position:fixed;inset:0;background:#000c;display:flex;align-items:center;justify-content:center;z-index:1000}.image-modal__content{position:relative;max-width:90vw;max-height:90vh}.image-modal__content img{max-width:90vw;max-height:90vh;object-fit:contain;display:block}.image-modal__close{position:fixed;top:15px;right:30px;background:transparent;border:none;color:#fff;font-size:60px;line-height:1;cursor:pointer}@media screen and (max-width: 650px){#product_page_carousel_thumbs{height:57px!important}#product_page_carousel_thumbs .carousel-thumb img{height:50px!important}}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
1266
+ }], ctorParameters: () => [{ type: IconCacheService }, { type: ProductConnectorService }, { type: ProductEventService }, { type: i0.ChangeDetectorRef }, { type: i1.DomSanitizer }], propDecorators: { carousel: [{
1261
1267
  type: ViewChild,
1262
1268
  args: ['carousel', { read: ElementRef }]
1263
1269
  }], onEsc: [{
@@ -1836,10 +1842,15 @@ class ProductRelatedComponent {
1836
1842
  async _loadImages() {
1837
1843
  this.articleViewModels.forEach(avm => {
1838
1844
  if (avm.article.image) {
1839
- this._iOne.getImageForCoDocument(avm.article.image, false).then(imageContent => avm.imageData = imageContent.image);
1845
+ this._iOne.getImageForCoDocument(avm.article.image, false).then(imageContent => this._handleImageData(avm, imageContent));
1840
1846
  }
1841
1847
  });
1842
1848
  }
1849
+ _handleImageData(avm, imageContent) {
1850
+ if (imageContent && imageContent.image) {
1851
+ avm.imageData = imageContent.image;
1852
+ }
1853
+ }
1843
1854
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ProductRelatedComponent, deps: [{ token: ProductConnectorService }, { token: ProductEventService }, { token: ProductConnectorAdapterService }, { token: ProductSettingsService }], target: i0.ɵɵFactoryTarget.Component }); }
1844
1855
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: ProductRelatedComponent, isStandalone: false, selector: "app-product-related", inputs: { refType: "refType", label: "label", externalSource: "externalSource", isSmallModus: "isSmallModus", createFrozenArticle: "createFrozenArticle", articles: "articles" }, ngImport: i0, template: `
1845
1856
  @if (articles && articles.length > 0) {
@@ -3553,7 +3564,7 @@ class IoneProductComponent {
3553
3564
  // this.sku = "4387";
3554
3565
  // this.sku = "1000612725";
3555
3566
  // this.sku = '1000610952'; // elix = 1000234793
3556
- this.sku = '1000234793';
3567
+ // this.sku = 'A30';
3557
3568
  }
3558
3569
  async ngOnInit() {
3559
3570
  this._subs.push(this._appEventService.onAddToCart.subscribe((data) => {
@@ -3735,10 +3746,14 @@ class ImageCarouselModule {
3735
3746
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ImageCarouselModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
3736
3747
  static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.16", ngImport: i0, type: ImageCarouselModule, declarations: [ImageCarouselComponent], imports: [CommonModule,
3737
3748
  LoaderModule,
3738
- ScrollContainerModule], exports: [ImageCarouselComponent] }); }
3749
+ ScrollContainerModule,
3750
+ CdkConnectedOverlay,
3751
+ CdkOverlayOrigin,
3752
+ IconModule], exports: [ImageCarouselComponent] }); }
3739
3753
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ImageCarouselModule, imports: [CommonModule,
3740
3754
  LoaderModule,
3741
- ScrollContainerModule] }); }
3755
+ ScrollContainerModule,
3756
+ IconModule] }); }
3742
3757
  }
3743
3758
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ImageCarouselModule, decorators: [{
3744
3759
  type: NgModule,
@@ -3746,7 +3761,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
3746
3761
  imports: [
3747
3762
  CommonModule,
3748
3763
  LoaderModule,
3749
- ScrollContainerModule
3764
+ ScrollContainerModule,
3765
+ CdkConnectedOverlay,
3766
+ CdkOverlayOrigin,
3767
+ IconModule
3750
3768
  ],
3751
3769
  declarations: [
3752
3770
  ImageCarouselComponent