@colijnit/product 260.1.1 → 260.1.3

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.
@@ -23,16 +23,17 @@ import * as i1$1 from '@angular/platform-browser';
23
23
  import { DomSanitizer } from '@angular/platform-browser';
24
24
  import { IconModule, LoaderModule, ScrollContainerModule, PriceDisplayPipeModule, NumberPickerModule, ButtonModule, FilesUploadModule, TileModule } from '@colijnit/corecomponents';
25
25
  import { CoDocument } from '@colijnit/mainapi/build/model/co-document.bo';
26
+ import { OverlayModule } from '@angular/cdk/overlay';
27
+ import { IconModule as IconModule$1, LoaderModule as LoaderModule$1, ArticleTileModule, IconCollapseHandleModule, ButtonModule as ButtonModule$1 } from '@colijnit/corecomponents_v12';
26
28
  import { ConfiguratorStatisticsEnvironment } from '@colijnit/articleapi/build/model/configurator-statistics-environment';
27
- import { LoaderModule as LoaderModule$1, ArticleTileModule, IconCollapseHandleModule, ButtonModule as ButtonModule$1 } from '@colijnit/corecomponents_v12';
28
29
 
29
30
  // this file is dynamically created, do not change this
30
31
  class Version {
31
32
  constructor() {
32
33
  this.name = "@colijnit/product";
33
34
  this.description = "Product detail page project for iOne";
34
- this.symVer = "260.1.1";
35
- this.publishDate = "15-12-2025 17:02:25";
35
+ this.symVer = "260.1.3";
36
+ this.publishDate = "24-3-2026 16:21:40";
36
37
  }
37
38
  }
38
39
 
@@ -1349,11 +1350,13 @@ ProductSelectorTypeModule.decorators = [
1349
1350
  class ImageViewModel {
1350
1351
  }
1351
1352
  class ImageCarouselComponent {
1352
- constructor(_ione, _appEventService, _changeDetector, _domSanitizer) {
1353
+ constructor(iconCacheService, _ione, _appEventService, _changeDetector, _domSanitizer) {
1354
+ this.iconCacheService = iconCacheService;
1353
1355
  this._ione = _ione;
1354
1356
  this._appEventService = _appEventService;
1355
1357
  this._changeDetector = _changeDetector;
1356
1358
  this._domSanitizer = _domSanitizer;
1359
+ this.icons = IconEnum;
1357
1360
  this.isPopupOpen = false;
1358
1361
  this.showRefresh = false;
1359
1362
  this.resizing = false;
@@ -1491,51 +1494,63 @@ class ImageCarouselComponent {
1491
1494
  const ctx = resizeCanvas.getContext('2d');
1492
1495
  const img = document.createElement('img');
1493
1496
  img.crossOrigin = 'anonymous';
1494
- img.onload = () => {
1495
- ctx.imageSmoothingEnabled = true;
1496
- ctx.imageSmoothingQuality = 'high';
1497
- const ow = img.width;
1498
- const oh = img.height;
1499
- const aspect = ow / oh;
1500
- let newW = this._resizeCanvasHeight;
1501
- let newH = this._resizeCanvasHeight;
1502
- if (ow > oh) {
1503
- newH = this._resizeCanvasHeight / aspect;
1504
- }
1505
- else {
1506
- newW = this._resizeCanvasHeight * aspect;
1507
- }
1508
- resizeCanvas.width = Math.round(newW);
1509
- resizeCanvas.height = Math.round(newH);
1510
- // Ensure transparent background before drawing
1511
- ctx.clearRect(0, 0, resizeCanvas.width, resizeCanvas.height);
1512
- ctx.drawImage(img, 0, 0, resizeCanvas.width, resizeCanvas.height);
1513
- const mime = this._detectPreferredMime(source);
1514
- const resizedSource = mime === 'image/jpeg'
1515
- ? resizeCanvas.toDataURL('image/jpeg', 0.92) // only if original was JPEG
1516
- : resizeCanvas.toDataURL(mime); // PNG/WebP keep alpha
1517
- imageViewModel.source = this._domSanitizer.bypassSecurityTrustUrl(resizedSource);
1497
+ const handleFallback = () => {
1498
+ // When a CDN does not accept our tainted canvas, we fallback to the original source.
1499
+ imageViewModel.source = this._domSanitizer.bypassSecurityTrustUrl(source);
1518
1500
  imageViewModel.originalSource = source;
1519
1501
  this._changeDetector.detectChanges();
1520
1502
  };
1503
+ img.onerror = () => {
1504
+ handleFallback();
1505
+ };
1506
+ img.onload = () => {
1507
+ try {
1508
+ ctx.imageSmoothingEnabled = true;
1509
+ ctx.imageSmoothingQuality = 'high';
1510
+ const aspect = img.width / img.height;
1511
+ let newW = this._resizeCanvasHeight;
1512
+ let newH = this._resizeCanvasHeight;
1513
+ if (img.width > img.height) {
1514
+ newH = this._resizeCanvasHeight / aspect;
1515
+ }
1516
+ else {
1517
+ newW = this._resizeCanvasHeight * aspect;
1518
+ }
1519
+ resizeCanvas.width = Math.round(newW);
1520
+ resizeCanvas.height = Math.round(newH);
1521
+ ctx.clearRect(0, 0, resizeCanvas.width, resizeCanvas.height);
1522
+ ctx.drawImage(img, 0, 0, resizeCanvas.width, resizeCanvas.height);
1523
+ const mime = this._handleMimeTypes(source);
1524
+ const resizedSource = resizeCanvas.toDataURL(mime);
1525
+ imageViewModel.source = this._domSanitizer.bypassSecurityTrustUrl(resizedSource);
1526
+ imageViewModel.originalSource = source;
1527
+ this._changeDetector.detectChanges();
1528
+ }
1529
+ catch (error) {
1530
+ handleFallback();
1531
+ }
1532
+ };
1521
1533
  img.src = source;
1522
1534
  }
1523
- _detectPreferredMime(source) {
1524
- // Data URI check
1525
- const m = source.match(/^data:(image\/[a-zA-Z+.-]+);base64,/);
1526
- if (m) {
1527
- const t = m[1].toLowerCase();
1528
- if (t === 'image/png' || t === 'image/webp' || t === 'image/jpeg')
1529
- return t;
1535
+ _handleMimeTypes(imageSource) {
1536
+ // When we got base64 data
1537
+ if (imageSource.startsWith('data:')) {
1538
+ const match = imageSource.match(/^data:(image\/(png|webp|jpeg|jpg))/i);
1539
+ return match ? match[1].toLowerCase() : 'image/png';
1540
+ }
1541
+ // when we got an URL.
1542
+ const extension = imageSource.substring(imageSource.lastIndexOf('.') + 1).toLowerCase();
1543
+ switch (extension) {
1544
+ case 'png':
1545
+ return 'image/png';
1546
+ case 'webp':
1547
+ return 'image/webp';
1548
+ case 'jpg':
1549
+ case 'jpeg':
1550
+ return 'image/jpeg';
1551
+ default:
1552
+ return 'image/png';
1530
1553
  }
1531
- const lower = source.toLowerCase();
1532
- if (lower.endsWith('.png'))
1533
- return 'image/png';
1534
- if (lower.endsWith('.webp'))
1535
- return 'image/webp';
1536
- if (lower.endsWith('.jpg') || lower.endsWith('.jpeg'))
1537
- return 'image/jpeg';
1538
- return 'image/png';
1539
1554
  }
1540
1555
  _scrollCarouselToIndex() {
1541
1556
  if (this.currentIndex > -1 && this.currentIndex <= this.images.length) {
@@ -1554,65 +1569,63 @@ ImageCarouselComponent.decorators = [
1554
1569
  { type: Component, args: [{
1555
1570
  selector: 'app-image-carousel',
1556
1571
  template: `
1557
- <div id="product_page_carousel">
1558
- <div id="product_page_carousel_items">
1559
- <co-loader [isShown]="true" *ngIf="showLoader"></co-loader>
1560
- <div #carousel class="inner-carousel">
1561
- <!-- This has been taken out of the for loop to prevent flashing images when updating. -->
1562
- <div *ngIf="imageViewModels[0]" class="carousel-item" [id]="'slide-0'" [class.active]="isCurrentIndex(0)" (click)="handleShowImage(imageViewModels[0])">
1563
- <img [src]="imageViewModels[0].source">
1564
- </div>
1565
- <div *ngFor="let imageViewModel of imageViewModels.slice(1); let index = index" class="carousel-item"
1566
- [id]="'slide-' + (index + 1)" [class.active]="isCurrentIndex((index + 1))" (click)="handleShowImage(imageViewModel)">
1567
- <img [src]="imageViewModel.source">
1568
- </div>
1569
- <div class="carousel-scroller-layer" *ngIf="imageViewModels && imageViewModels.length > 1">
1570
- <div class="carousel-item-scroller prev" (click)="gotoPrevSlide()" *ngIf="currentIndex > 0"></div>
1571
- <div class="carousel-item-scroller next" (click)="gotoNextSlide()" *ngIf="currentIndex < images.length - 1"></div>
1572
- </div>
1573
- </div>
1574
- <!--
1575
- <co-icon class="selector-type-icon refresh-button" [iconData]="iconCache.getIcon(icons.Refresh)" (click)="onForceRenderImage()" [class.loading]="showLoader" *ngIf="showRefresh"></co-icon>
1576
- -->
1572
+ <div id="product_page_carousel">
1573
+ <div id="product_page_carousel_items">
1574
+ <co-loader [isShown]="true" *ngIf="showLoader"></co-loader>
1575
+ <div #carousel class="inner-carousel">
1576
+ <!-- This has been taken out of the for loop to prevent flashing images when updating. -->
1577
+ <div *ngIf="imageViewModels[0]" class="carousel-item" [id]="'slide-0'" [class.active]="isCurrentIndex(0)"
1578
+ (click)="handleShowImage(imageViewModels[0])">
1579
+ <img [src]="imageViewModels[0].source">
1577
1580
  </div>
1581
+ <div *ngFor="let imageViewModel of imageViewModels.slice(1); let index = index" class="carousel-item"
1582
+ [id]="'slide-' + (index + 1)" [class.active]="isCurrentIndex((index + 1))" (click)="handleShowImage(imageViewModel)">
1583
+ <img [src]="imageViewModel.source">
1584
+ </div>
1585
+ <div class="carousel-scroller-layer" *ngIf="imageViewModels && imageViewModels.length > 1">
1586
+ <div class="carousel-item-scroller prev" (click)="gotoPrevSlide()" *ngIf="currentIndex > 0"></div>
1587
+ <div class="carousel-item-scroller next" (click)="gotoNextSlide()" *ngIf="currentIndex < images.length - 1"></div>
1588
+ </div>
1589
+ </div>
1590
+ <!--
1591
+ <co-icon class="selector-type-icon refresh-button" [iconData]="iconCache.getIcon(icons.Refresh)" (click)="onForceRenderImage()" [class.loading]="showLoader" *ngIf="showRefresh"></co-icon>
1592
+ -->
1593
+ </div>
1578
1594
 
1579
- <div id="product_page_carousel_thumbs">
1580
- <co-scroll-container class="scroll-container" *ngIf="imageViewModels && imageViewModels.length > 1">
1581
- <div *ngFor="let imageViewModel of imageViewModels; let index = index" class="carousel-thumb"
1582
- [class.active]="index === currentIndex">
1583
- <img [src]="imageViewModel.source" (click)="handleThumbClick(index)"/>
1584
- </div>
1585
- </co-scroll-container>
1595
+ <div id="product_page_carousel_thumbs">
1596
+ <co-scroll-container class="scroll-container" *ngIf="imageViewModels && imageViewModels.length > 1">
1597
+ <div *ngFor="let imageViewModel of imageViewModels; let index = index" class="carousel-thumb"
1598
+ [class.active]="index === currentIndex">
1599
+ <img [src]="imageViewModel.source" (click)="handleThumbClick(index)"/>
1586
1600
  </div>
1601
+ </co-scroll-container>
1587
1602
  </div>
1588
- <!-- Modal (real popup) -->
1589
- <div
1590
- class="image-modal"
1591
- *ngIf="isPopupOpen"
1592
- (click)="closePopup()"
1593
- role="dialog"
1594
- aria-modal="true"
1595
- aria-label="Image preview"
1596
- >
1597
- <div class="image-modal__content" (click)="$event.stopPropagation()">
1598
- <button
1599
- class="image-modal__close"
1600
- type="button"
1601
- aria-label="Close"
1602
- (click)="closePopup()"
1603
- >
1604
- ×
1605
- </button>
1603
+ </div>
1606
1604
 
1607
- <img [src]="selectedImage?.originalSource" alt="Image preview" />
1605
+ <div cdkOverlayOrigin #trigger="cdkOverlayOrigin"></div>
1606
+ <ng-template
1607
+ cdkConnectedOverlay
1608
+ [cdkConnectedOverlayOrigin]="trigger"
1609
+ [cdkConnectedOverlayOpen]="isPopupOpen"
1610
+ [cdkConnectedOverlayHasBackdrop]="true"
1611
+ (backdropClick)="closePopup()">
1612
+ <div class="image-modal" role="dialog" aria-modal="true" aria-label="Image preview">
1613
+ <div class="image-modal__content">
1614
+ <button class="image-modal__close" type="button" aria-label="Close" (click)="closePopup()">
1615
+ <co-icon [iconData]="iconCacheService.getIcon(icons.CrossSkinny)"></co-icon>
1616
+ </button>
1617
+ <img [src]="selectedImage?.originalSource" alt="Image preview"/>
1608
1618
  </div>
1609
1619
  </div>
1620
+ </ng-template>
1621
+
1610
1622
  `,
1611
1623
  changeDetection: ChangeDetectionStrategy.OnPush,
1612
1624
  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;-o-object-fit:contain;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:rgba(0,0,0,.8);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;-o-object-fit:contain;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"]
1613
1625
  },] }
1614
1626
  ];
1615
1627
  ImageCarouselComponent.ctorParameters = () => [
1628
+ { type: IconCacheService },
1616
1629
  { type: ProductConnectorService },
1617
1630
  { type: ProductEventService },
1618
1631
  { type: ChangeDetectorRef },
@@ -1637,7 +1650,9 @@ ImageCarouselModule.decorators = [
1637
1650
  CommonModule,
1638
1651
  LoaderModule,
1639
1652
  // IconModule,
1640
- ScrollContainerModule
1653
+ ScrollContainerModule,
1654
+ OverlayModule,
1655
+ IconModule$1
1641
1656
  ],
1642
1657
  declarations: [
1643
1658
  ImageCarouselComponent
@@ -2064,6 +2079,14 @@ ProductAddtocartModule.decorators = [
2064
2079
  },] }
2065
2080
  ];
2066
2081
 
2082
+ class ArticleViewModel {
2083
+ constructor(article, imageData) {
2084
+ this.article = article;
2085
+ if (imageData) {
2086
+ this.imageData = imageData;
2087
+ }
2088
+ }
2089
+ }
2067
2090
  class ProductRelatedComponent {
2068
2091
  constructor(_iOne, _appEventService, _productConnectorAdapterService, _settingsService) {
2069
2092
  this._iOne = _iOne;
@@ -2072,7 +2095,7 @@ class ProductRelatedComponent {
2072
2095
  this._settingsService = _settingsService;
2073
2096
  this.isSmallModus = true;
2074
2097
  this.createFrozenArticle = true;
2075
- this.imageDataMap = new Map();
2098
+ this.articleViewModels = [];
2076
2099
  this._articles = [];
2077
2100
  }
2078
2101
  set articles(value) {
@@ -2089,6 +2112,8 @@ class ProductRelatedComponent {
2089
2112
  else {
2090
2113
  this._articles = value;
2091
2114
  }
2115
+ this.articleViewModels.length = 0;
2116
+ this._articles.forEach(a => this.articleViewModels.push(new ArticleViewModel(a)));
2092
2117
  this._loadImages();
2093
2118
  }
2094
2119
  }
@@ -2133,14 +2158,18 @@ class ProductRelatedComponent {
2133
2158
  }
2134
2159
  _loadImages() {
2135
2160
  return __awaiter(this, void 0, void 0, function* () {
2136
- for (const a of this._articles) {
2137
- if (a.image) {
2138
- const imageContent = yield this._iOne.getImageForCoDocument(a.image, false);
2139
- this.imageDataMap.set(a.goodId, imageContent.image);
2161
+ this.articleViewModels.forEach(avm => {
2162
+ if (avm.article.image) {
2163
+ this._iOne.getImageForCoDocument(avm.article.image, false).then(imageContent => this._handleImageData(avm, imageContent));
2140
2164
  }
2141
- }
2165
+ });
2142
2166
  });
2143
2167
  }
2168
+ _handleImageData(avm, imageContent) {
2169
+ if (imageContent && imageContent.image) {
2170
+ avm.imageData = imageContent.image;
2171
+ }
2172
+ }
2144
2173
  }
2145
2174
  ProductRelatedComponent.decorators = [
2146
2175
  { type: Component, args: [{
@@ -2149,19 +2178,19 @@ ProductRelatedComponent.decorators = [
2149
2178
  <div *ngIf="articles && articles.length > 0">
2150
2179
  <app-header [label]="label" [amount]="articles?.length" *ngIf="label"></app-header>
2151
2180
  <co-scroll-container>
2152
- <div class="article-wrapper" *ngFor="let article of articles">
2181
+ <div class="article-wrapper" *ngFor="let articleViewModel of articleViewModels">
2153
2182
  <co-article-tile
2154
- [imageData]="imageDataMap.get(article.goodId)"
2155
- [description]="article.description"
2156
- [price]="article.price"
2157
- [level]="article.stockStatus"
2183
+ [imageData]="articleViewModel.imageData"
2184
+ [description]="articleViewModel.article.description"
2185
+ [price]="articleViewModel.article.price"
2186
+ [level]="articleViewModel.article.stockStatus"
2158
2187
  [hasCartButton]="true"
2159
2188
  [isSmallModus]="isSmallModus"
2160
- [hasConfigureButton]="article.goodType === 'B'"
2161
- (contentClick)="handleContentClick(article)"
2162
- (cartButtonClick)="handleAddToCartClick(article)"
2163
- (configureButtonClick)="handleContentClick(article)"
2164
- (threeDButtonClick)="handleThreeDButtonClick(article)"
2189
+ [hasConfigureButton]="articleViewModel.article.goodType === 'B'"
2190
+ (contentClick)="handleContentClick(articleViewModel.article)"
2191
+ (cartButtonClick)="handleAddToCartClick(articleViewModel.article)"
2192
+ (configureButtonClick)="handleContentClick(articleViewModel.article)"
2193
+ (threeDButtonClick)="handleThreeDButtonClick(articleViewModel.article)"
2165
2194
  ></co-article-tile>
2166
2195
  </div>
2167
2196
  </co-scroll-container>