@maro-tech/card 0.1.13 → 0.1.14

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.
@@ -1,10 +1,11 @@
1
1
  import * as i1$1 from '@angular/common';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
- import { input, inject, DestroyRef, signal, Component, computed, viewChildren, output, effect, HostListener, Input, InjectionToken, forwardRef, Injectable, ViewChild, untracked } from '@angular/core';
4
+ import { input, inject, DestroyRef, signal, Component, computed, viewChild, viewChildren, output, effect, HostListener, Input, InjectionToken, forwardRef, Injectable, ViewChild, untracked } from '@angular/core';
5
5
  import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
6
6
  import { EventBus, UiButtonComponent, UiToastService, UiModalComponent, UiConfirmService } from '@maro-tech/core';
7
7
  import { DomSanitizer } from '@angular/platform-browser';
8
+ import Panzoom from '@panzoom/panzoom';
8
9
  import * as i1 from 'lucide-angular';
9
10
  import { Download, LucideAngularModule, Eye, FileText, Plus, Sparkles, Trash } from 'lucide-angular';
10
11
  import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
@@ -279,11 +280,13 @@ class ImagesViewerComponent {
279
280
  currentIndex = signal(0, ...(ngDevMode ? [{ debugName: "currentIndex" }] : /* istanbul ignore next */ []));
280
281
  isOriginalVisible = signal(false, ...(ngDevMode ? [{ debugName: "isOriginalVisible" }] : /* istanbul ignore next */ []));
281
282
  originalPreviewUnavailable = signal(false, ...(ngDevMode ? [{ debugName: "originalPreviewUnavailable" }] : /* istanbul ignore next */ []));
283
+ currentImage = viewChild('currentImage', ...(ngDevMode ? [{ debugName: "currentImage" }] : /* istanbul ignore next */ []));
282
284
  thumbnailButtons = viewChildren('thumbnailButton', ...(ngDevMode ? [{ debugName: "thumbnailButtons" }] : /* istanbul ignore next */ []));
283
285
  closed = output();
284
286
  touchStartX = 0;
285
287
  touchStartY = 0;
286
288
  lastImagesKey = '';
289
+ panzoomInstance = null;
287
290
  constructor() {
288
291
  effect(() => {
289
292
  const urls = this.urls();
@@ -307,6 +310,28 @@ class ImagesViewerComponent {
307
310
  const thumbnail = this.thumbnailButtons()[this.currentIndex()];
308
311
  thumbnail?.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
309
312
  });
313
+ effect((onCleanup) => {
314
+ const image = this.currentImage()?.nativeElement;
315
+ this.currentIndex();
316
+ this.isOriginalVisible();
317
+ if (!image)
318
+ return;
319
+ const panzoom = Panzoom(image, {
320
+ maxScale: 4,
321
+ minScale: 1,
322
+ step: 0.5,
323
+ panOnlyWhenZoomed: true,
324
+ pinchAndPan: true,
325
+ touchAction: 'none',
326
+ animate: true,
327
+ });
328
+ this.panzoomInstance = panzoom;
329
+ onCleanup(() => {
330
+ panzoom.destroy();
331
+ if (this.panzoomInstance === panzoom)
332
+ this.panzoomInstance = null;
333
+ });
334
+ });
310
335
  }
311
336
  onEscape() {
312
337
  this.closed.emit();
@@ -331,6 +356,8 @@ class ImagesViewerComponent {
331
356
  const touch = event.changedTouches[0];
332
357
  if (!touch)
333
358
  return;
359
+ if (this.panzoomInstance?.getScale() && this.panzoomInstance.getScale() > 1.01)
360
+ return;
334
361
  const deltaX = touch.clientX - this.touchStartX;
335
362
  const deltaY = touch.clientY - this.touchStartY;
336
363
  if (Math.abs(deltaX) < 50 || Math.abs(deltaX) <= Math.abs(deltaY))
@@ -360,6 +387,38 @@ class ImagesViewerComponent {
360
387
  this.originalPreviewUnavailable.set(false);
361
388
  this.isOriginalVisible.update((visible) => !visible);
362
389
  }
390
+ zoomIn() {
391
+ this.panzoomInstance?.zoomIn({ animate: true });
392
+ }
393
+ zoomOut() {
394
+ this.panzoomInstance?.zoomOut({ animate: true });
395
+ }
396
+ resetZoom() {
397
+ this.panzoomInstance?.reset({ animate: true });
398
+ }
399
+ onWheel(event) {
400
+ event.preventDefault();
401
+ const panzoom = this.panzoomInstance;
402
+ if (!panzoom)
403
+ return;
404
+ const direction = event.deltaY < 0 ? 1 : -1;
405
+ const scale = panzoom.getScale() * Math.exp((direction * 0.5) / 3);
406
+ panzoom.zoomToPoint(scale, { clientX: event.clientX, clientY: event.clientY }, { animate: false });
407
+ }
408
+ onImageLoad() {
409
+ this.panzoomInstance?.reset({ animate: false });
410
+ }
411
+ toggleZoom() {
412
+ const panzoom = this.panzoomInstance;
413
+ if (!panzoom)
414
+ return;
415
+ if (panzoom.getScale() > 1.01) {
416
+ panzoom.reset({ animate: true });
417
+ }
418
+ else {
419
+ panzoom.zoomIn({ animate: true });
420
+ }
421
+ }
363
422
  selectImage(index, event) {
364
423
  this.currentIndex.set(index);
365
424
  this.originalPreviewUnavailable.set(false);
@@ -408,12 +467,12 @@ class ImagesViewerComponent {
408
467
  return String(url || '').replace(/\/preview(?:\?[^#]*)?$/i, '');
409
468
  }
410
469
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ImagesViewerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
411
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: ImagesViewerComponent, isStandalone: true, selector: "app-images-viewer", inputs: { urls: { classPropertyName: "urls", publicName: "urls", isSignal: true, isRequired: false, transformFunction: null }, initialIndex: { classPropertyName: "initialIndex", publicName: "initialIndex", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, host: { listeners: { "document:keydown.escape": "onEscape()", "document:keydown.arrowleft": "onArrowLeft()", "document:keydown.arrowright": "onArrowRight()" } }, viewQueries: [{ propertyName: "thumbnailButtons", predicate: ["thumbnailButton"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"fixed inset-0 z-[2300] flex items-center justify-center bg-black/80 p-3 md:p-4\" role=\"dialog\" aria-modal=\"true\" aria-label=\"\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u0434 \u0444\u043E\u0442\u043E\">\n <button type=\"button\" class=\"absolute inset-0\" aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438 \u043F\u0435\u0440\u0435\u0433\u043B\u044F\u0434\" (click)=\"closeViewer()\"></button>\n <div class=\"relative z-[1] flex h-full w-full max-w-[1500px] flex-col items-center justify-center gap-4\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"absolute right-2 top-2 z-20 inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white md:right-3 md:top-3\"\n aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438\"\n (click)=\"closeViewer()\">\n \u2715\n </button>\n\n <div\n class=\"relative flex min-h-0 w-full flex-1 items-center justify-center overflow-hidden rounded-[18px] touch-pan-y\"\n (touchstart)=\"onTouchStart($event)\"\n (touchend)=\"onTouchEnd($event)\">\n <div class=\"h-full w-full overflow-hidden rounded-[18px]\">\n <div\n class=\"flex h-full w-full\"\n style=\"will-change: transform; backface-visibility: hidden;\"\n [style.transition]=\"imageViewerTransitionStyle\"\n [style.transform]=\"imageViewerTransform\">\n @for (url of urls(); track $index; let idx = $index) {\n <div class=\"flex h-full w-full min-w-full shrink-0 items-center justify-center overflow-hidden\">\n @if (isPreloadIndex(idx)) {\n @if (idx === currentIndex() && isOriginalVisible() && originalPreviewUnavailable()) {\n <div class=\"max-w-md rounded-2xl bg-white/95 px-6 py-5 text-center text-sm text-slate-700 shadow-xl\">\n <p class=\"font-semibold text-slate-900\">\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u0434 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B\u0443 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0438\u0439</p>\n <p class=\"mt-1\">\u0426\u0435\u0439 \u0444\u0430\u0439\u043B \u043C\u043E\u0436\u043D\u0430 \u043B\u0438\u0448\u0435 \u0437\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0438\u0442\u0438.</p>\n </div>\n } @else {\n <img\n [src]=\"getImageUrl(url, idx)\"\n [attr.alt]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n [attr.loading]=\"idx === currentIndex() ? 'eager' : 'lazy'\"\n decoding=\"async\"\n (error)=\"onImageError(idx)\"\n class=\"block max-h-[74vh] max-w-[96vw] object-contain md:max-h-[78vh]\">\n }\n }\n </div>\n }\n </div>\n </div>\n\n @if (urls().length > 1) {\n <button type=\"button\" class=\"absolute left-0 top-0 z-10 flex h-full w-1/4 min-w-12 items-center justify-start pl-2 md:pl-3\" aria-label=\"\u041F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u0454 \u0444\u043E\u0442\u043E\" (click)=\"showPreviousImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u2039</span>\n </button>\n <button type=\"button\" class=\"absolute right-0 top-0 z-10 flex h-full w-1/4 min-w-12 items-center justify-end pr-2 md:pr-3\" aria-label=\"\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0435 \u0444\u043E\u0442\u043E\" (click)=\"showNextImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u203A</span>\n </button>\n }\n\n @if (currentUrl) {\n <div class=\"absolute bottom-3 right-3 z-20 inline-flex max-w-[calc(100%-1.5rem)] items-center gap-1.5 rounded-2xl bg-black/55 p-1.5 text-white shadow-[0_12px_28px_rgba(15,23,42,0.24)] backdrop-blur-md md:bottom-4 md:right-4\">\n <button\n type=\"button\"\n class=\"inline-flex h-9 items-center gap-2 rounded-xl px-2.5 text-xs font-semibold transition hover:bg-white/10\"\n [class.bg-emerald-500\\/20]=\"isOriginalVisible()\"\n [class.text-emerald-100]=\"isOriginalVisible()\"\n [attr.aria-pressed]=\"isOriginalVisible()\"\n aria-label=\"\u041F\u0435\u0440\u0435\u043C\u043A\u043D\u0443\u0442\u0438 \u043C\u0456\u0436 \u043F\u0440\u0435\u0432\u2019\u044E \u0442\u0430 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B\u043E\u043C\"\n (click)=\"toggleOriginal()\">\n <span>\u041E\u0440\u0438\u0433\u0456\u043D\u0430\u043B</span>\n <span\n class=\"relative inline-flex h-5 w-9 items-center rounded-full border p-0.5 transition-colors\"\n [class.bg-emerald-500]=\"isOriginalVisible()\"\n [class.bg-white\\/15]=\"!isOriginalVisible()\"\n [class.border-emerald-200]=\"isOriginalVisible()\"\n [class.border-white\\/45]=\"!isOriginalVisible()\"\n [class.ring-2]=\"isOriginalVisible()\"\n [class.ring-emerald-300\\/70]=\"isOriginalVisible()\">\n <span\n class=\"h-4 w-4 rounded-full bg-white shadow-sm transition-transform duration-200\"\n [class.translate-x-4]=\"isOriginalVisible()\">\n </span>\n </span>\n </button>\n <a\n class=\"inline-flex h-9 w-9 items-center justify-center rounded-xl transition hover:bg-white/10\"\n [href]=\"getOriginalUrl(currentUrl)\"\n download\n aria-label=\"\u0421\u043A\u0430\u0447\u0430\u0442\u0438 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B \u0444\u043E\u0442\u043E\">\n <lucide-icon [img]=\"downloadIcon\" [size]=\"19\" [strokeWidth]=\"2.25\"></lucide-icon>\n </a>\n </div>\n }\n </div>\n\n @if (urls().length > 1) {\n <div class=\"flex w-full flex-col items-center gap-3 pb-1 md:pb-2\">\n <div class=\"flex w-full max-w-[92vw] flex-nowrap justify-start gap-2 overflow-x-auto overflow-y-hidden overscroll-x-contain px-2 py-3 md:max-w-[80vw]\">\n @for (url of urls(); track url; let idx = $index) {\n <button\n #thumbnailButton\n type=\"button\"\n class=\"h-14 w-14 shrink-0 overflow-hidden rounded-xl border-2 border-transparent bg-white p-1 transition-transform duration-200\"\n [class.border-slate-900]=\"idx === currentIndex()\"\n [class.mx-2]=\"idx === currentIndex()\"\n [style.transform]=\"idx === currentIndex() ? 'scale(1.4)' : 'scale(1)'\"\n [class.origin-center]=\"idx === currentIndex()\"\n [class.relative]=\"idx === currentIndex()\"\n [class.z-10]=\"idx === currentIndex()\"\n [attr.aria-current]=\"idx === currentIndex() ? 'true' : null\"\n [attr.aria-label]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n (click)=\"selectImage(idx, $event)\">\n <img class=\"block aspect-square w-full rounded-lg object-contain\" [src]=\"getThumbnailUrl(url)\" [attr.alt]=\"'\u041C\u0456\u043D\u0456\u0430\u0442\u044E\u0440\u0430 ' + (idx + 1)\" loading=\"lazy\" decoding=\"async\">\n </button>\n }\n </div>\n <div class=\"rounded-full bg-white/95 px-3 py-1.5 text-xs font-semibold text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.2)]\">\n {{ currentIndex() + 1 }} / {{ urls().length }}\n </div>\n </div>\n }\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }] });
470
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: ImagesViewerComponent, isStandalone: true, selector: "app-images-viewer", inputs: { urls: { classPropertyName: "urls", publicName: "urls", isSignal: true, isRequired: false, transformFunction: null }, initialIndex: { classPropertyName: "initialIndex", publicName: "initialIndex", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, host: { listeners: { "document:keydown.escape": "onEscape()", "document:keydown.arrowleft": "onArrowLeft()", "document:keydown.arrowright": "onArrowRight()" } }, viewQueries: [{ propertyName: "currentImage", first: true, predicate: ["currentImage"], descendants: true, isSignal: true }, { propertyName: "thumbnailButtons", predicate: ["thumbnailButton"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"fixed inset-0 z-[2300] flex items-center justify-center bg-black/80 p-3 md:p-4\" role=\"dialog\" aria-modal=\"true\" aria-label=\"\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u0434 \u0444\u043E\u0442\u043E\">\n <button type=\"button\" class=\"absolute inset-0\" aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438 \u043F\u0435\u0440\u0435\u0433\u043B\u044F\u0434\" (click)=\"closeViewer()\"></button>\n <div class=\"relative z-[1] flex h-full w-full max-w-[1500px] flex-col items-center justify-center gap-4\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"absolute right-2 top-2 z-20 inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white md:right-3 md:top-3\"\n aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438\"\n (click)=\"closeViewer()\">\n \u2715\n </button>\n\n <div\n class=\"relative flex min-h-0 w-full flex-1 items-center justify-center overflow-hidden rounded-[18px]\"\n (touchstart)=\"onTouchStart($event)\"\n (touchend)=\"onTouchEnd($event)\">\n <div class=\"h-full w-full overflow-hidden rounded-[18px]\">\n <div\n class=\"flex h-full w-full\"\n style=\"will-change: transform; backface-visibility: hidden;\"\n [style.transition]=\"imageViewerTransitionStyle\"\n [style.transform]=\"imageViewerTransform\">\n @for (url of urls(); track $index; let idx = $index) {\n <div class=\"flex h-full w-full min-w-full shrink-0 items-center justify-center overflow-hidden\" (wheel)=\"onWheel($event)\">\n @if (isPreloadIndex(idx)) {\n @if (idx === currentIndex() && isOriginalVisible() && originalPreviewUnavailable()) {\n <div class=\"max-w-md rounded-2xl bg-white/95 px-6 py-5 text-center text-sm text-slate-700 shadow-xl\">\n <p class=\"font-semibold text-slate-900\">\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u0434 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B\u0443 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0438\u0439</p>\n <p class=\"mt-1\">\u0426\u0435\u0439 \u0444\u0430\u0439\u043B \u043C\u043E\u0436\u043D\u0430 \u043B\u0438\u0448\u0435 \u0437\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0438\u0442\u0438.</p>\n </div>\n } @else {\n @if (idx === currentIndex()) {\n <img\n #currentImage\n [src]=\"getImageUrl(url, idx)\"\n [attr.alt]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n loading=\"eager\"\n decoding=\"async\"\n (load)=\"onImageLoad()\"\n (error)=\"onImageError(idx)\"\n (dblclick)=\"toggleZoom()\"\n class=\"block max-h-[74vh] max-w-[96vw] object-contain md:max-h-[78vh]\">\n } @else {\n <img\n [src]=\"getImageUrl(url, idx)\"\n [attr.alt]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n loading=\"lazy\"\n decoding=\"async\"\n (error)=\"onImageError(idx)\"\n class=\"block max-h-[74vh] max-w-[96vw] object-contain md:max-h-[78vh]\">\n }\n }\n }\n </div>\n }\n </div>\n </div>\n\n @if (urls().length > 1) {\n <button type=\"button\" class=\"absolute left-2 top-1/2 z-10 inline-flex h-11 w-11 -translate-y-1/2 items-center justify-center rounded-full md:left-3\" aria-label=\"\u041F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u0454 \u0444\u043E\u0442\u043E\" (click)=\"showPreviousImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u2039</span>\n </button>\n <button type=\"button\" class=\"absolute right-2 top-1/2 z-10 inline-flex h-11 w-11 -translate-y-1/2 items-center justify-center rounded-full md:right-3\" aria-label=\"\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0435 \u0444\u043E\u0442\u043E\" (click)=\"showNextImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u203A</span>\n </button>\n }\n\n @if (currentUrl) {\n <div class=\"absolute bottom-3 right-3 z-20 inline-flex max-w-[calc(100%-1.5rem)] items-center gap-1.5 rounded-2xl bg-black/55 p-1.5 text-white shadow-[0_12px_28px_rgba(15,23,42,0.24)] backdrop-blur-md md:bottom-4 md:right-4\">\n <button type=\"button\" class=\"inline-flex h-9 w-9 items-center justify-center rounded-xl text-lg transition hover:bg-white/10\" aria-label=\"\u0417\u0431\u0456\u043B\u044C\u0448\u0438\u0442\u0438 \u0444\u043E\u0442\u043E\" (click)=\"zoomIn()\">+</button>\n <button type=\"button\" class=\"inline-flex h-9 w-9 items-center justify-center rounded-xl text-lg transition hover:bg-white/10\" aria-label=\"\u0417\u043C\u0435\u043D\u0448\u0438\u0442\u0438 \u0444\u043E\u0442\u043E\" (click)=\"zoomOut()\">\u2212</button>\n <button type=\"button\" class=\"inline-flex h-9 items-center justify-center rounded-xl px-2 text-xs font-semibold transition hover:bg-white/10\" aria-label=\"\u0421\u043A\u0438\u043D\u0443\u0442\u0438 \u043C\u0430\u0441\u0448\u0442\u0430\u0431\" (click)=\"resetZoom()\">100%</button>\n <button\n type=\"button\"\n class=\"inline-flex h-9 items-center gap-2 rounded-xl px-2.5 text-xs font-semibold transition hover:bg-white/10\"\n [class.bg-emerald-500\\/20]=\"isOriginalVisible()\"\n [class.text-emerald-100]=\"isOriginalVisible()\"\n [attr.aria-pressed]=\"isOriginalVisible()\"\n aria-label=\"\u041F\u0435\u0440\u0435\u043C\u043A\u043D\u0443\u0442\u0438 \u043C\u0456\u0436 \u043F\u0440\u0435\u0432\u2019\u044E \u0442\u0430 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B\u043E\u043C\"\n (click)=\"toggleOriginal()\">\n <span>\u041E\u0440\u0438\u0433\u0456\u043D\u0430\u043B</span>\n <span\n class=\"relative inline-flex h-5 w-9 items-center rounded-full border p-0.5 transition-colors\"\n [class.bg-emerald-500]=\"isOriginalVisible()\"\n [class.bg-white\\/15]=\"!isOriginalVisible()\"\n [class.border-emerald-200]=\"isOriginalVisible()\"\n [class.border-white\\/45]=\"!isOriginalVisible()\"\n [class.ring-2]=\"isOriginalVisible()\"\n [class.ring-emerald-300\\/70]=\"isOriginalVisible()\">\n <span\n class=\"h-4 w-4 rounded-full bg-white shadow-sm transition-transform duration-200\"\n [class.translate-x-4]=\"isOriginalVisible()\">\n </span>\n </span>\n </button>\n <a\n class=\"inline-flex h-9 w-9 items-center justify-center rounded-xl transition hover:bg-white/10\"\n [href]=\"getOriginalUrl(currentUrl)\"\n download\n aria-label=\"\u0421\u043A\u0430\u0447\u0430\u0442\u0438 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B \u0444\u043E\u0442\u043E\">\n <lucide-icon [img]=\"downloadIcon\" [size]=\"19\" [strokeWidth]=\"2.25\"></lucide-icon>\n </a>\n </div>\n }\n </div>\n\n @if (urls().length > 1) {\n <div class=\"flex w-full flex-col items-center gap-3 pb-1 md:pb-2\">\n <div class=\"flex w-full max-w-[92vw] flex-nowrap justify-start gap-2 overflow-x-auto overflow-y-hidden overscroll-x-contain px-2 py-3 md:max-w-[80vw]\">\n @for (url of urls(); track url; let idx = $index) {\n <button\n #thumbnailButton\n type=\"button\"\n class=\"h-14 w-14 shrink-0 overflow-hidden rounded-xl border-2 border-transparent bg-white p-1 transition-transform duration-200\"\n [class.border-slate-900]=\"idx === currentIndex()\"\n [class.mx-2]=\"idx === currentIndex()\"\n [style.transform]=\"idx === currentIndex() ? 'scale(1.4)' : 'scale(1)'\"\n [class.origin-center]=\"idx === currentIndex()\"\n [class.relative]=\"idx === currentIndex()\"\n [class.z-10]=\"idx === currentIndex()\"\n [attr.aria-current]=\"idx === currentIndex() ? 'true' : null\"\n [attr.aria-label]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n (click)=\"selectImage(idx, $event)\">\n <img class=\"block aspect-square w-full rounded-lg object-contain\" [src]=\"getThumbnailUrl(url)\" [attr.alt]=\"'\u041C\u0456\u043D\u0456\u0430\u0442\u044E\u0440\u0430 ' + (idx + 1)\" loading=\"lazy\" decoding=\"async\">\n </button>\n }\n </div>\n <div class=\"rounded-full bg-white/95 px-3 py-1.5 text-xs font-semibold text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.2)]\">\n {{ currentIndex() + 1 }} / {{ urls().length }}\n </div>\n </div>\n }\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }] });
412
471
  }
413
472
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ImagesViewerComponent, decorators: [{
414
473
  type: Component,
415
- args: [{ selector: 'app-images-viewer', standalone: true, imports: [CommonModule, LucideAngularModule], template: "<div class=\"fixed inset-0 z-[2300] flex items-center justify-center bg-black/80 p-3 md:p-4\" role=\"dialog\" aria-modal=\"true\" aria-label=\"\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u0434 \u0444\u043E\u0442\u043E\">\n <button type=\"button\" class=\"absolute inset-0\" aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438 \u043F\u0435\u0440\u0435\u0433\u043B\u044F\u0434\" (click)=\"closeViewer()\"></button>\n <div class=\"relative z-[1] flex h-full w-full max-w-[1500px] flex-col items-center justify-center gap-4\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"absolute right-2 top-2 z-20 inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white md:right-3 md:top-3\"\n aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438\"\n (click)=\"closeViewer()\">\n \u2715\n </button>\n\n <div\n class=\"relative flex min-h-0 w-full flex-1 items-center justify-center overflow-hidden rounded-[18px] touch-pan-y\"\n (touchstart)=\"onTouchStart($event)\"\n (touchend)=\"onTouchEnd($event)\">\n <div class=\"h-full w-full overflow-hidden rounded-[18px]\">\n <div\n class=\"flex h-full w-full\"\n style=\"will-change: transform; backface-visibility: hidden;\"\n [style.transition]=\"imageViewerTransitionStyle\"\n [style.transform]=\"imageViewerTransform\">\n @for (url of urls(); track $index; let idx = $index) {\n <div class=\"flex h-full w-full min-w-full shrink-0 items-center justify-center overflow-hidden\">\n @if (isPreloadIndex(idx)) {\n @if (idx === currentIndex() && isOriginalVisible() && originalPreviewUnavailable()) {\n <div class=\"max-w-md rounded-2xl bg-white/95 px-6 py-5 text-center text-sm text-slate-700 shadow-xl\">\n <p class=\"font-semibold text-slate-900\">\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u0434 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B\u0443 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0438\u0439</p>\n <p class=\"mt-1\">\u0426\u0435\u0439 \u0444\u0430\u0439\u043B \u043C\u043E\u0436\u043D\u0430 \u043B\u0438\u0448\u0435 \u0437\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0438\u0442\u0438.</p>\n </div>\n } @else {\n <img\n [src]=\"getImageUrl(url, idx)\"\n [attr.alt]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n [attr.loading]=\"idx === currentIndex() ? 'eager' : 'lazy'\"\n decoding=\"async\"\n (error)=\"onImageError(idx)\"\n class=\"block max-h-[74vh] max-w-[96vw] object-contain md:max-h-[78vh]\">\n }\n }\n </div>\n }\n </div>\n </div>\n\n @if (urls().length > 1) {\n <button type=\"button\" class=\"absolute left-0 top-0 z-10 flex h-full w-1/4 min-w-12 items-center justify-start pl-2 md:pl-3\" aria-label=\"\u041F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u0454 \u0444\u043E\u0442\u043E\" (click)=\"showPreviousImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u2039</span>\n </button>\n <button type=\"button\" class=\"absolute right-0 top-0 z-10 flex h-full w-1/4 min-w-12 items-center justify-end pr-2 md:pr-3\" aria-label=\"\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0435 \u0444\u043E\u0442\u043E\" (click)=\"showNextImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u203A</span>\n </button>\n }\n\n @if (currentUrl) {\n <div class=\"absolute bottom-3 right-3 z-20 inline-flex max-w-[calc(100%-1.5rem)] items-center gap-1.5 rounded-2xl bg-black/55 p-1.5 text-white shadow-[0_12px_28px_rgba(15,23,42,0.24)] backdrop-blur-md md:bottom-4 md:right-4\">\n <button\n type=\"button\"\n class=\"inline-flex h-9 items-center gap-2 rounded-xl px-2.5 text-xs font-semibold transition hover:bg-white/10\"\n [class.bg-emerald-500\\/20]=\"isOriginalVisible()\"\n [class.text-emerald-100]=\"isOriginalVisible()\"\n [attr.aria-pressed]=\"isOriginalVisible()\"\n aria-label=\"\u041F\u0435\u0440\u0435\u043C\u043A\u043D\u0443\u0442\u0438 \u043C\u0456\u0436 \u043F\u0440\u0435\u0432\u2019\u044E \u0442\u0430 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B\u043E\u043C\"\n (click)=\"toggleOriginal()\">\n <span>\u041E\u0440\u0438\u0433\u0456\u043D\u0430\u043B</span>\n <span\n class=\"relative inline-flex h-5 w-9 items-center rounded-full border p-0.5 transition-colors\"\n [class.bg-emerald-500]=\"isOriginalVisible()\"\n [class.bg-white\\/15]=\"!isOriginalVisible()\"\n [class.border-emerald-200]=\"isOriginalVisible()\"\n [class.border-white\\/45]=\"!isOriginalVisible()\"\n [class.ring-2]=\"isOriginalVisible()\"\n [class.ring-emerald-300\\/70]=\"isOriginalVisible()\">\n <span\n class=\"h-4 w-4 rounded-full bg-white shadow-sm transition-transform duration-200\"\n [class.translate-x-4]=\"isOriginalVisible()\">\n </span>\n </span>\n </button>\n <a\n class=\"inline-flex h-9 w-9 items-center justify-center rounded-xl transition hover:bg-white/10\"\n [href]=\"getOriginalUrl(currentUrl)\"\n download\n aria-label=\"\u0421\u043A\u0430\u0447\u0430\u0442\u0438 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B \u0444\u043E\u0442\u043E\">\n <lucide-icon [img]=\"downloadIcon\" [size]=\"19\" [strokeWidth]=\"2.25\"></lucide-icon>\n </a>\n </div>\n }\n </div>\n\n @if (urls().length > 1) {\n <div class=\"flex w-full flex-col items-center gap-3 pb-1 md:pb-2\">\n <div class=\"flex w-full max-w-[92vw] flex-nowrap justify-start gap-2 overflow-x-auto overflow-y-hidden overscroll-x-contain px-2 py-3 md:max-w-[80vw]\">\n @for (url of urls(); track url; let idx = $index) {\n <button\n #thumbnailButton\n type=\"button\"\n class=\"h-14 w-14 shrink-0 overflow-hidden rounded-xl border-2 border-transparent bg-white p-1 transition-transform duration-200\"\n [class.border-slate-900]=\"idx === currentIndex()\"\n [class.mx-2]=\"idx === currentIndex()\"\n [style.transform]=\"idx === currentIndex() ? 'scale(1.4)' : 'scale(1)'\"\n [class.origin-center]=\"idx === currentIndex()\"\n [class.relative]=\"idx === currentIndex()\"\n [class.z-10]=\"idx === currentIndex()\"\n [attr.aria-current]=\"idx === currentIndex() ? 'true' : null\"\n [attr.aria-label]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n (click)=\"selectImage(idx, $event)\">\n <img class=\"block aspect-square w-full rounded-lg object-contain\" [src]=\"getThumbnailUrl(url)\" [attr.alt]=\"'\u041C\u0456\u043D\u0456\u0430\u0442\u044E\u0440\u0430 ' + (idx + 1)\" loading=\"lazy\" decoding=\"async\">\n </button>\n }\n </div>\n <div class=\"rounded-full bg-white/95 px-3 py-1.5 text-xs font-semibold text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.2)]\">\n {{ currentIndex() + 1 }} / {{ urls().length }}\n </div>\n </div>\n }\n </div>\n</div>\n" }]
416
- }], ctorParameters: () => [], propDecorators: { urls: [{ type: i0.Input, args: [{ isSignal: true, alias: "urls", required: false }] }], initialIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialIndex", required: false }] }], thumbnailButtons: [{ type: i0.ViewChildren, args: ['thumbnailButton', { isSignal: true }] }], closed: [{ type: i0.Output, args: ["closed"] }], onEscape: [{
474
+ args: [{ selector: 'app-images-viewer', standalone: true, imports: [CommonModule, LucideAngularModule], template: "<div class=\"fixed inset-0 z-[2300] flex items-center justify-center bg-black/80 p-3 md:p-4\" role=\"dialog\" aria-modal=\"true\" aria-label=\"\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u0434 \u0444\u043E\u0442\u043E\">\n <button type=\"button\" class=\"absolute inset-0\" aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438 \u043F\u0435\u0440\u0435\u0433\u043B\u044F\u0434\" (click)=\"closeViewer()\"></button>\n <div class=\"relative z-[1] flex h-full w-full max-w-[1500px] flex-col items-center justify-center gap-4\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"absolute right-2 top-2 z-20 inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white md:right-3 md:top-3\"\n aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438\"\n (click)=\"closeViewer()\">\n \u2715\n </button>\n\n <div\n class=\"relative flex min-h-0 w-full flex-1 items-center justify-center overflow-hidden rounded-[18px]\"\n (touchstart)=\"onTouchStart($event)\"\n (touchend)=\"onTouchEnd($event)\">\n <div class=\"h-full w-full overflow-hidden rounded-[18px]\">\n <div\n class=\"flex h-full w-full\"\n style=\"will-change: transform; backface-visibility: hidden;\"\n [style.transition]=\"imageViewerTransitionStyle\"\n [style.transform]=\"imageViewerTransform\">\n @for (url of urls(); track $index; let idx = $index) {\n <div class=\"flex h-full w-full min-w-full shrink-0 items-center justify-center overflow-hidden\" (wheel)=\"onWheel($event)\">\n @if (isPreloadIndex(idx)) {\n @if (idx === currentIndex() && isOriginalVisible() && originalPreviewUnavailable()) {\n <div class=\"max-w-md rounded-2xl bg-white/95 px-6 py-5 text-center text-sm text-slate-700 shadow-xl\">\n <p class=\"font-semibold text-slate-900\">\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u0434 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B\u0443 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0438\u0439</p>\n <p class=\"mt-1\">\u0426\u0435\u0439 \u0444\u0430\u0439\u043B \u043C\u043E\u0436\u043D\u0430 \u043B\u0438\u0448\u0435 \u0437\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0438\u0442\u0438.</p>\n </div>\n } @else {\n @if (idx === currentIndex()) {\n <img\n #currentImage\n [src]=\"getImageUrl(url, idx)\"\n [attr.alt]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n loading=\"eager\"\n decoding=\"async\"\n (load)=\"onImageLoad()\"\n (error)=\"onImageError(idx)\"\n (dblclick)=\"toggleZoom()\"\n class=\"block max-h-[74vh] max-w-[96vw] object-contain md:max-h-[78vh]\">\n } @else {\n <img\n [src]=\"getImageUrl(url, idx)\"\n [attr.alt]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n loading=\"lazy\"\n decoding=\"async\"\n (error)=\"onImageError(idx)\"\n class=\"block max-h-[74vh] max-w-[96vw] object-contain md:max-h-[78vh]\">\n }\n }\n }\n </div>\n }\n </div>\n </div>\n\n @if (urls().length > 1) {\n <button type=\"button\" class=\"absolute left-2 top-1/2 z-10 inline-flex h-11 w-11 -translate-y-1/2 items-center justify-center rounded-full md:left-3\" aria-label=\"\u041F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u0454 \u0444\u043E\u0442\u043E\" (click)=\"showPreviousImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u2039</span>\n </button>\n <button type=\"button\" class=\"absolute right-2 top-1/2 z-10 inline-flex h-11 w-11 -translate-y-1/2 items-center justify-center rounded-full md:right-3\" aria-label=\"\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0435 \u0444\u043E\u0442\u043E\" (click)=\"showNextImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u203A</span>\n </button>\n }\n\n @if (currentUrl) {\n <div class=\"absolute bottom-3 right-3 z-20 inline-flex max-w-[calc(100%-1.5rem)] items-center gap-1.5 rounded-2xl bg-black/55 p-1.5 text-white shadow-[0_12px_28px_rgba(15,23,42,0.24)] backdrop-blur-md md:bottom-4 md:right-4\">\n <button type=\"button\" class=\"inline-flex h-9 w-9 items-center justify-center rounded-xl text-lg transition hover:bg-white/10\" aria-label=\"\u0417\u0431\u0456\u043B\u044C\u0448\u0438\u0442\u0438 \u0444\u043E\u0442\u043E\" (click)=\"zoomIn()\">+</button>\n <button type=\"button\" class=\"inline-flex h-9 w-9 items-center justify-center rounded-xl text-lg transition hover:bg-white/10\" aria-label=\"\u0417\u043C\u0435\u043D\u0448\u0438\u0442\u0438 \u0444\u043E\u0442\u043E\" (click)=\"zoomOut()\">\u2212</button>\n <button type=\"button\" class=\"inline-flex h-9 items-center justify-center rounded-xl px-2 text-xs font-semibold transition hover:bg-white/10\" aria-label=\"\u0421\u043A\u0438\u043D\u0443\u0442\u0438 \u043C\u0430\u0441\u0448\u0442\u0430\u0431\" (click)=\"resetZoom()\">100%</button>\n <button\n type=\"button\"\n class=\"inline-flex h-9 items-center gap-2 rounded-xl px-2.5 text-xs font-semibold transition hover:bg-white/10\"\n [class.bg-emerald-500\\/20]=\"isOriginalVisible()\"\n [class.text-emerald-100]=\"isOriginalVisible()\"\n [attr.aria-pressed]=\"isOriginalVisible()\"\n aria-label=\"\u041F\u0435\u0440\u0435\u043C\u043A\u043D\u0443\u0442\u0438 \u043C\u0456\u0436 \u043F\u0440\u0435\u0432\u2019\u044E \u0442\u0430 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B\u043E\u043C\"\n (click)=\"toggleOriginal()\">\n <span>\u041E\u0440\u0438\u0433\u0456\u043D\u0430\u043B</span>\n <span\n class=\"relative inline-flex h-5 w-9 items-center rounded-full border p-0.5 transition-colors\"\n [class.bg-emerald-500]=\"isOriginalVisible()\"\n [class.bg-white\\/15]=\"!isOriginalVisible()\"\n [class.border-emerald-200]=\"isOriginalVisible()\"\n [class.border-white\\/45]=\"!isOriginalVisible()\"\n [class.ring-2]=\"isOriginalVisible()\"\n [class.ring-emerald-300\\/70]=\"isOriginalVisible()\">\n <span\n class=\"h-4 w-4 rounded-full bg-white shadow-sm transition-transform duration-200\"\n [class.translate-x-4]=\"isOriginalVisible()\">\n </span>\n </span>\n </button>\n <a\n class=\"inline-flex h-9 w-9 items-center justify-center rounded-xl transition hover:bg-white/10\"\n [href]=\"getOriginalUrl(currentUrl)\"\n download\n aria-label=\"\u0421\u043A\u0430\u0447\u0430\u0442\u0438 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B \u0444\u043E\u0442\u043E\">\n <lucide-icon [img]=\"downloadIcon\" [size]=\"19\" [strokeWidth]=\"2.25\"></lucide-icon>\n </a>\n </div>\n }\n </div>\n\n @if (urls().length > 1) {\n <div class=\"flex w-full flex-col items-center gap-3 pb-1 md:pb-2\">\n <div class=\"flex w-full max-w-[92vw] flex-nowrap justify-start gap-2 overflow-x-auto overflow-y-hidden overscroll-x-contain px-2 py-3 md:max-w-[80vw]\">\n @for (url of urls(); track url; let idx = $index) {\n <button\n #thumbnailButton\n type=\"button\"\n class=\"h-14 w-14 shrink-0 overflow-hidden rounded-xl border-2 border-transparent bg-white p-1 transition-transform duration-200\"\n [class.border-slate-900]=\"idx === currentIndex()\"\n [class.mx-2]=\"idx === currentIndex()\"\n [style.transform]=\"idx === currentIndex() ? 'scale(1.4)' : 'scale(1)'\"\n [class.origin-center]=\"idx === currentIndex()\"\n [class.relative]=\"idx === currentIndex()\"\n [class.z-10]=\"idx === currentIndex()\"\n [attr.aria-current]=\"idx === currentIndex() ? 'true' : null\"\n [attr.aria-label]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n (click)=\"selectImage(idx, $event)\">\n <img class=\"block aspect-square w-full rounded-lg object-contain\" [src]=\"getThumbnailUrl(url)\" [attr.alt]=\"'\u041C\u0456\u043D\u0456\u0430\u0442\u044E\u0440\u0430 ' + (idx + 1)\" loading=\"lazy\" decoding=\"async\">\n </button>\n }\n </div>\n <div class=\"rounded-full bg-white/95 px-3 py-1.5 text-xs font-semibold text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.2)]\">\n {{ currentIndex() + 1 }} / {{ urls().length }}\n </div>\n </div>\n }\n </div>\n</div>\n" }]
475
+ }], ctorParameters: () => [], propDecorators: { urls: [{ type: i0.Input, args: [{ isSignal: true, alias: "urls", required: false }] }], initialIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialIndex", required: false }] }], currentImage: [{ type: i0.ViewChild, args: ['currentImage', { isSignal: true }] }], thumbnailButtons: [{ type: i0.ViewChildren, args: ['thumbnailButton', { isSignal: true }] }], closed: [{ type: i0.Output, args: ["closed"] }], onEscape: [{
417
476
  type: HostListener,
418
477
  args: ['document:keydown.escape']
419
478
  }], onArrowLeft: [{