@herdwatch/ngx-ionic-image-viewer
Version:
The angular workspace to develop the ngx-ionic-image-viewer component
403 lines (394 loc) • 22.4 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, Component, CUSTOM_ELEMENTS_SCHEMA, Input, ViewChild, ViewEncapsulation, Directive, HostListener, makeEnvironmentProviders } from '@angular/core';
import * as i1 from '@ionic/angular/standalone';
import { IonHeader, IonToolbar, IonButton, IonButtons, IonIcon, IonTitle, IonContent, IonFooter, IonText } from '@ionic/angular/standalone';
import { NgClass } from '@angular/common';
import { addIcons } from 'ionicons';
import { close } from 'ionicons/icons';
import { register } from 'swiper/element/bundle';
class NgxIonicImageViewerService {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.10", ngImport: i0, type: NgxIonicImageViewerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.10", ngImport: i0, type: NgxIonicImageViewerService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.10", ngImport: i0, type: NgxIonicImageViewerService, decorators: [{
type: Injectable
}] });
class ViewerModalComponent {
// @ViewChild('sliderRef', { static: true }) slides!: IonSlides;
constructor(modalController) {
this.modalController = modalController;
this.alt = '';
this.scheme = 'auto';
this.slideOptions = {};
this.srcFallback = '';
this.srcHighRes = '';
this.swipeToClose = true;
this.text = '';
this.title = '';
this.titleSize = '';
this.defaultSlideOptions = {
zoom: {
enabled: true,
},
};
this.options = {};
this.swipeState = {
phase: 'init',
direction: 'none',
swipeType: 'none',
startX: 0,
startY: 0,
distance: 0,
distanceX: 0,
distanceY: 0,
threshold: 150, // required min distance traveled to be considered swipe
restraint: 100, // maximum distance allowed at the same time in perpendicular direction
allowedTime: 500, // maximum time allowed to travel that distance
elapsedTime: 0,
startTime: 0,
};
addIcons({ close });
}
async ngOnInit() {
this.options = { ...this.defaultSlideOptions, ...this.slideOptions };
this.src = this.srcHighRes || this.src;
this.setStyle();
this.setScheme(this.scheme);
this.initSwipeToClose(this.swipeToClose);
/**
* Current Workaround
* See reported bug: https://github.com/ionic-team/ionic/issues/19638#issuecomment-584828315
* Hint: Comment in '<ion-slide>' in component
*/
}
setStyle() {
const el = document.querySelector('.ion-img-viewer');
el?.style.setProperty('--height', '100%');
el?.style.setProperty('--width', '100%');
el?.style.setProperty('--border-radius', '0');
}
setScheme(scheme) {
if (scheme && scheme === 'auto') {
return;
}
const el = document.querySelector('.ion-img-viewer');
if (this.scheme === 'light') {
el?.style.setProperty('--ion-background-color', '#ffffff');
el?.style.setProperty('--ion-background-color-rgb', '255, 255, 255');
el?.style.setProperty('--ion-text-color', '#000');
el?.style.setProperty('--ion-text-color-rgb', '0,0,0');
}
if (this.scheme === 'dark') {
if (el?.classList.contains('ios')) {
el?.style.setProperty('--ion-background-color', '#000000');
el?.style.setProperty('--ion-background-color-rgb', '0, 0, 0');
}
else {
el?.style.setProperty('--ion-background-color', '#121212');
el?.style.setProperty('--ion-background-color-rgb', '18,18,18');
}
el?.style.setProperty('--ion-text-color', '#ffffff');
el?.style.setProperty('--ion-text-color-rgb', '255,255,255');
}
}
/**
* @see http://www.javascriptkit.com/javatutors/touchevents3.shtml
*/
initSwipeToClose(isActive = true) {
if (!isActive) {
return;
}
const el = document.querySelector('ion-modal');
el?.addEventListener('mousedown', (event) => this.swipeStart(event), true);
el?.addEventListener('mousemove', (event) => this.swipeMove(event), true);
el?.addEventListener('mouseup', () => this.swipeEnd(), true);
el?.addEventListener('touchstart', (event) => this.swipeStart(event), true);
el?.addEventListener('touchmove', (event) => this.swipeMove(event), true);
el?.addEventListener('touchend', () => this.swipeEnd(), true);
this.modalController.getTop().then((modal) => {
modal?.onWillDismiss().then(() => {
document.removeEventListener('mousedown', this.swipeStart, true);
document.removeEventListener('mousemove', this.swipeMove, true);
document.removeEventListener('mouseup', this.swipeMove, true);
document.removeEventListener('touchstart', this.swipeStart, true);
document.removeEventListener('touchmove', this.swipeMove, true);
document.removeEventListener('touchend', this.swipeMove, true);
});
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
swipeStart(event) {
const { pageX, pageY } = event.type === 'touchstart' && event?.changedTouches
? event.changedTouches[0]
: event;
this.swipeState = {
...this.swipeState,
phase: 'start',
direction: 'none',
distance: 0,
startX: pageX,
startY: pageY,
startTime: new Date().getTime(),
};
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
swipeMove(event) {
const { pageX, pageY } = event.type === 'touchmove' && event?.changedTouches
? event.changedTouches[0]
: event;
// get horizontal dist traveled by finger while in contact with surface
const distanceX = pageX - this.swipeState.startX;
// get vertical dist traveled by finger while in contact with surface
const distanceY = pageY - this.swipeState.startY;
let direction;
let distance;
if (Math.abs(distanceX) > Math.abs(distanceY)) {
// if distance traveled horizontally is greater than vertically, consider this a horizontal swipe
direction = distanceX < 0 ? 'left' : 'right';
distance = distanceX;
}
else {
// else consider this a vertical swipe
direction = distanceY < 0 ? 'up' : 'down';
distance = distanceY;
}
this.swipeState = {
...this.swipeState,
phase: 'move',
direction,
distance,
distanceX,
distanceY,
};
if (event.cancelable) {
event.preventDefault();
}
}
swipeEnd() {
if (this.swipeState.phase === 'none') {
return;
}
const { allowedTime, direction, restraint, startTime, threshold, distanceX, distanceY, } = this.swipeState;
let swipeType = null;
const elapsedTime = new Date().getTime() - startTime; // get time elapsed
if (elapsedTime <= allowedTime) {
// first condition for a swipe met
if (Math.abs(distanceX) >= threshold &&
Math.abs(distanceY) <= restraint) {
// 2nd condition for horizontal swipe met
swipeType = direction; // set swipeType to either "left" or "right"
}
else if (Math.abs(distanceY) >= threshold &&
Math.abs(distanceX) <= restraint) {
// 2nd condition for vertical swipe met
swipeType = direction; // set swipeType to either "top" or "down"
}
}
this.swipeState = {
...this.swipeState,
phase: 'end',
swipeType: swipeType ?? '',
};
if (swipeType === 'down') {
return this.closeModal();
}
}
closeModal() {
this.modalController.dismiss();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.10", ngImport: i0, type: ViewerModalComponent, deps: [{ token: i1.ModalController }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.10", type: ViewerModalComponent, isStandalone: true, selector: "ion-viewer-modal", inputs: { alt: "alt", scheme: "scheme", slideOptions: "slideOptions", src: "src", srcFallback: "srcFallback", srcHighRes: "srcHighRes", swipeToClose: "swipeToClose", text: "text", title: "title", titleSize: "titleSize" }, viewQueries: [{ propertyName: "swiperRef", first: true, predicate: ["swiper"], descendants: true }], ngImport: i0, template: "<ion-header [ngClass]=\"{ 'no-title': title && title.length <= 0 }\">\n <ion-toolbar>\n <ion-buttons slot=\"primary\">\n <ion-button data-cy=\"closeModal\" (click)=\"closeModal()\">\n <ion-icon slot=\"icon-only\" name=\"close\"></ion-icon>\n </ion-button>\n </ion-buttons>\n <ion-title [size]=\"titleSize\">{{ title }}</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content [forceOverscroll]=\"false\">\n <swiper-container [options]=\"options\" centeredSlides=\"true\" passiveListeners=\"false\" zoom=\"true\" #swiper>\n <swiper-slide>\n <div class=\"swiper-zoom-container\">\n <img [alt]=\"this.alt\" [src]=\"this.src\"/>\n </div>\n </swiper-slide>\n </swiper-container>\n</ion-content>\n\n<ion-footer [ngClass]=\"{ 'no-text': text && text.length <= 0 }\">\n <ion-toolbar class=\"ion-text-center\">\n <ion-text>{{ text }}</ion-text>\n </ion-toolbar>\n</ion-footer>\n", styles: ["ion-slides{--height: 100%;height:100%}ion-toolbar{--border-style: none;--background: rgba(var(--ion-background-color-rgb, (255, 255, 255)), .6);background:rgba(var(--ion-background-color-rgb, 255, 255, 255),.6)}ion-header{opacity:1;position:absolute}ion-header.no-title:after{content:none}ion-header.no-title ion-toolbar{--background: rgba(0, 0, 0, 0);background:#0000}ion-footer{position:absolute;bottom:0}ion-footer.no-text:before{content:none}ion-footer.no-text ion-toolbar{--background: rgba(0, 0, 0, 0);background:#0000}swiper-container{width:100%;height:100%}swiper-slide{overflow:hidden}\n"], dependencies: [{ kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { 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: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.10", ngImport: i0, type: ViewerModalComponent, decorators: [{
type: Component,
args: [{ selector: 'ion-viewer-modal', standalone: true, schemas: [CUSTOM_ELEMENTS_SCHEMA], imports: [
IonHeader,
IonToolbar,
IonButton,
NgClass,
IonButtons,
IonIcon,
IonTitle,
IonContent,
IonFooter,
IonText,
], template: "<ion-header [ngClass]=\"{ 'no-title': title && title.length <= 0 }\">\n <ion-toolbar>\n <ion-buttons slot=\"primary\">\n <ion-button data-cy=\"closeModal\" (click)=\"closeModal()\">\n <ion-icon slot=\"icon-only\" name=\"close\"></ion-icon>\n </ion-button>\n </ion-buttons>\n <ion-title [size]=\"titleSize\">{{ title }}</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content [forceOverscroll]=\"false\">\n <swiper-container [options]=\"options\" centeredSlides=\"true\" passiveListeners=\"false\" zoom=\"true\" #swiper>\n <swiper-slide>\n <div class=\"swiper-zoom-container\">\n <img [alt]=\"this.alt\" [src]=\"this.src\"/>\n </div>\n </swiper-slide>\n </swiper-container>\n</ion-content>\n\n<ion-footer [ngClass]=\"{ 'no-text': text && text.length <= 0 }\">\n <ion-toolbar class=\"ion-text-center\">\n <ion-text>{{ text }}</ion-text>\n </ion-toolbar>\n</ion-footer>\n", styles: ["ion-slides{--height: 100%;height:100%}ion-toolbar{--border-style: none;--background: rgba(var(--ion-background-color-rgb, (255, 255, 255)), .6);background:rgba(var(--ion-background-color-rgb, 255, 255, 255),.6)}ion-header{opacity:1;position:absolute}ion-header.no-title:after{content:none}ion-header.no-title ion-toolbar{--background: rgba(0, 0, 0, 0);background:#0000}ion-footer{position:absolute;bottom:0}ion-footer.no-text:before{content:none}ion-footer.no-text ion-toolbar{--background: rgba(0, 0, 0, 0);background:#0000}swiper-container{width:100%;height:100%}swiper-slide{overflow:hidden}\n"] }]
}], ctorParameters: () => [{ type: i1.ModalController }], propDecorators: { alt: [{
type: Input
}], scheme: [{
type: Input
}], slideOptions: [{
type: Input
}], src: [{
type: Input
}], srcFallback: [{
type: Input
}], srcHighRes: [{
type: Input
}], swipeToClose: [{
type: Input
}], text: [{
type: Input
}], title: [{
type: Input
}], titleSize: [{
type: Input
}], swiperRef: [{
type: ViewChild,
args: ['swiper']
}] } });
register();
class NgxIonicImageViewerComponent {
constructor(modalController) {
this.modalController = modalController;
}
async viewImage(src, srcFallback = '', srcHighRes = '', title = '', titleSize = '', text = '', scheme = 'auto', slideOptions = {}, swipeToClose = true) {
const modal = await this.modalController.create({
component: ViewerModalComponent,
componentProps: {
src,
srcFallback,
srcHighRes,
title,
titleSize,
text,
scheme,
slideOptions,
swipeToClose
},
cssClass: this.cssClass && this.cssClass instanceof Array
? ['ion-img-viewer', ...this.cssClass]
: ['ion-img-viewer', this.cssClass],
keyboardClose: true,
showBackdrop: true
});
return await modal.present();
}
ngOnInit() {
/* do nothing */
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.10", ngImport: i0, type: NgxIonicImageViewerComponent, deps: [{ token: i1.ModalController }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.10", type: NgxIonicImageViewerComponent, isStandalone: true, selector: "ion-img-viewer", inputs: { alt: "alt", cssClass: "cssClass", scheme: "scheme", slideOptions: "slideOptions", src: "src", srcFallback: "srcFallback", srcHighRes: "srcHighRes", swipeToClose: "swipeToClose", text: "text", title: "title", titleSize: "titleSize" }, ngImport: i0, template: "<img\n [alt]=\"alt\"\n [src]=\"src\"\n (click)=\"viewImage(src, srcFallback, srcHighRes, title, titleSize, text, scheme, slideOptions, swipeToClose)\"\n (error)=\"src = srcFallback\"\n/>\n", styles: [":host{display:block}\n"] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.10", ngImport: i0, type: NgxIonicImageViewerComponent, decorators: [{
type: Component,
args: [{ selector: 'ion-img-viewer', encapsulation: ViewEncapsulation.Emulated, standalone: true, template: "<img\n [alt]=\"alt\"\n [src]=\"src\"\n (click)=\"viewImage(src, srcFallback, srcHighRes, title, titleSize, text, scheme, slideOptions, swipeToClose)\"\n (error)=\"src = srcFallback\"\n/>\n", styles: [":host{display:block}\n"] }]
}], ctorParameters: () => [{ type: i1.ModalController }], propDecorators: { alt: [{
type: Input
}], cssClass: [{
type: Input
}], scheme: [{
type: Input
}], slideOptions: [{
type: Input
}], src: [{
type: Input
}], srcFallback: [{
type: Input
}], srcHighRes: [{
type: Input
}], swipeToClose: [{
type: Input
}], text: [{
type: Input
}], title: [{
type: Input
}], titleSize: [{
type: Input
}] } });
class NgxIonicImageViewerDirective {
constructor(el, renderer, modalController) {
this.el = el;
this.renderer = renderer;
this.modalController = modalController;
}
onClick() {
this.viewImage(this.src, this.srcFallback, this.srcHighRes, this.title, this.titleSize, this.text, this.scheme, this.slideOptions, this.swipeToClose);
}
onError() {
if (this.src !== this.el.nativeElement.src) {
this.src = this.el.nativeElement.src;
}
if (this.srcFallback) {
this.renderer.setAttribute(this.el.nativeElement, 'src', this.srcFallback);
}
}
ngOnInit() {
if (!this.el.nativeElement.hasAttribute('src')) {
this.renderer.setAttribute(this.el.nativeElement, 'src', this.src);
}
}
async viewImage(src, srcFallback = '', srcHighRes = '', title = '', titleSize = '', text = '', scheme = 'auto', slideOptions = {}, swipeToClose = true) {
const modal = await this.modalController.create({
component: ViewerModalComponent,
componentProps: {
src,
srcFallback,
srcHighRes,
title,
titleSize,
text,
scheme,
slideOptions,
swipeToClose
},
cssClass: this.cssClass && this.cssClass instanceof Array
? ['ion-img-viewer', ...this.cssClass]
: ['ion-img-viewer', this.cssClass],
keyboardClose: true,
showBackdrop: true
});
return await modal.present();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.10", ngImport: i0, type: NgxIonicImageViewerDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i1.ModalController }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.10", type: NgxIonicImageViewerDirective, isStandalone: true, selector: "[ionImgViewer]", inputs: { cssClass: "cssClass", scheme: "scheme", slideOptions: "slideOptions", src: "src", srcFallback: "srcFallback", srcHighRes: "srcHighRes", swipeToClose: "swipeToClose", text: "text", title: "title", titleSize: "titleSize" }, host: { listeners: { "click": "onClick()", "error": "onError($event)" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.10", ngImport: i0, type: NgxIonicImageViewerDirective, decorators: [{
type: Directive,
args: [{
selector: '[ionImgViewer]',
standalone: true,
}]
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i1.ModalController }], propDecorators: { cssClass: [{
type: Input
}], scheme: [{
type: Input
}], slideOptions: [{
type: Input
}], src: [{
type: Input
}], srcFallback: [{
type: Input
}], srcHighRes: [{
type: Input
}], swipeToClose: [{
type: Input
}], text: [{
type: Input
}], title: [{
type: Input
}], titleSize: [{
type: Input
}], onClick: [{
type: HostListener,
args: ['click']
}], onError: [{
type: HostListener,
args: ['error', ['$event']]
}] } });
function provideNgxIonicImageViewer() {
return [
NgxIonicImageViewerService
];
}
function provideEnvironmentNgxIonicImageViewer() {
return makeEnvironmentProviders(provideNgxIonicImageViewer());
}
/*
* Public API Surface of ngx-ionic-image-viewer
*/
/**
* Generated bundle index. Do not edit.
*/
export { NgxIonicImageViewerComponent, NgxIonicImageViewerDirective, NgxIonicImageViewerService, ViewerModalComponent, provideEnvironmentNgxIonicImageViewer, provideNgxIonicImageViewer };
//# sourceMappingURL=herdwatch-ngx-ionic-image-viewer.mjs.map