+[ ] Regression (a behavior that used to work and stopped working in a new release)
+[ ] Bug report
+[ ] Feature request
+[ ] Documentation issue or request
+[ ] Support request
+
+
+## Current behavior
+
+
+
+## Expected behavior
+
+
+
+## Minimal reproduction of the problem with instructions
+
+
+
+## What is the motivation / use case for changing the behavior?
+
+
+
+## Environment (the most important section to fill very carefully)
+
+
+- @ks89/angular-modal-gallery version: X.X.X
+- Node version: X.X.X
+- npm version: X.X.X
+- Operating System and version:
+- Angular version: X.Y.Z
+- angular-cli version (or SystemJS/Webpack): X.Y.Z
+- I'm using Server Side Rendering with angular-universal: YES/NO
+- I'm compiling with mode: DEBUG / PROD / PROD with AOT
+
+
+
+Browser:
+- [ ] Chrome (desktop) version XX
+- [ ] Chrome (Android) version XX
+- [ ] Chrome (iOS) version XX
+- [ ] Firefox version XX
+- [ ] Safari (desktop) version XX
+- [ ] Safari (iOS) version XX
+- [ ] IE version XX
+- [ ] Edge version XX
+
+Others:
+
+
@ks89/angular-modal-gallery is an Angular library (SSR compatible) to create image galleries.
+
+
+Despite its name, this library is more than for modal galleries, because I'm introducing new features every major release. In fact, It's composed by 3 main parts:
+
+
plain-gallery: shows either a row, a column or a grid of clickable thumbnails using pure flexbox
+
modal-gallery: is the core part of this project and display a modal window with full screen images, buttons, the current image and optionally, also navigation dots and previews
+
carousel: shows a configurable plain carousel (not modal) with auto-play and other cool features
+
+
@ks89/angular-modal-gallery supports also keyboard shortcuts, swipe gestures and mouse events.
+
diff --git a/examples/angular-cli-20/src/app/app.component.scss b/examples/angular-cli-20/src/app/app.component.scss
new file mode 100644
index 00000000..2383946c
--- /dev/null
+++ b/examples/angular-cli-20/src/app/app.component.scss
@@ -0,0 +1,47 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+
+#main-title {
+ text-align: center;
+ width: 100%;
+}
+
+#menu {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+
+ > .menu-item {
+ margin-left: 10px;
+ }
+}
+
+.copyright {
+ text-align: center;
+}
+
+.margin {
+ margin-left: 15px;
+ margin-right: 15px;
+}
diff --git a/examples/angular-cli-20/src/app/app.component.ts b/examples/angular-cli-20/src/app/app.component.ts
new file mode 100644
index 00000000..97621976
--- /dev/null
+++ b/examples/angular-cli-20/src/app/app.component.ts
@@ -0,0 +1,37 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+import { RouterOutlet } from '@angular/router';
+
+import { NavbarComponent } from './navbar/navbar.component';
+
+@Component({
+ selector: 'ks-root',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.scss'],
+ imports: [NavbarComponent, RouterOutlet]
+})
+export class AppComponent {
+}
diff --git a/examples/angular-cli-20/src/app/app.config.ts b/examples/angular-cli-20/src/app/app.config.ts
new file mode 100644
index 00000000..563e35ae
--- /dev/null
+++ b/examples/angular-cli-20/src/app/app.config.ts
@@ -0,0 +1,23 @@
+import { ApplicationConfig, importProvidersFrom, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
+import { provideRouter } from '@angular/router';
+import { BrowserModule } from '@angular/platform-browser';
+import { FormsModule } from '@angular/forms';
+
+import { GalleryModule } from '@ks89/angular-modal-gallery';
+
+import { routes } from './app.routes';
+
+export const appConfig: ApplicationConfig = {
+ providers: [
+ provideBrowserGlobalErrorListeners(),
+ provideZoneChangeDetection({ eventCoalescing: true }),
+ provideRouter(routes),
+ importProvidersFrom(
+ BrowserModule,
+ FormsModule,
+
+ // import GalleryModule. Install @ks89/angular-modal-gallery first
+ GalleryModule
+ )
+ ]
+};
diff --git a/examples/angular-cli-20/src/app/app.routes.ts b/examples/angular-cli-20/src/app/app.routes.ts
new file mode 100644
index 00000000..f91e522a
--- /dev/null
+++ b/examples/angular-cli-20/src/app/app.routes.ts
@@ -0,0 +1,13 @@
+import { Routes } from '@angular/router';
+
+import { ModalGalleryExampleComponent } from './modal-gallery/modal-gallery.component';
+import { PlainGalleryExampleComponent } from './plain-gallery/plain-gallery.component';
+import { CarouselExampleComponent } from './carousel/carousel.component';
+import { HomeComponent } from './home/home.component';
+
+export const routes: Routes = [
+ { path: '', component: HomeComponent },
+ { path: 'carousel', component: CarouselExampleComponent },
+ { path: 'modal', component: ModalGalleryExampleComponent },
+ { path: 'plain', component: PlainGalleryExampleComponent }
+];
diff --git a/examples/angular-cli-20/src/app/carousel/carousel.component.ts b/examples/angular-cli-20/src/app/carousel/carousel.component.ts
new file mode 100644
index 00000000..90cb52b0
--- /dev/null
+++ b/examples/angular-cli-20/src/app/carousel/carousel.component.ts
@@ -0,0 +1,571 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+
+import {
+ AccessibilityConfig, CarouselLibConfig, Image, ImageEvent,
+ ModalGalleryConfig, ModalGalleryRef, ModalGalleryService, GalleryModule
+} from '@ks89/angular-modal-gallery';
+
+@Component({
+ selector: 'ks-carousel-page',
+ templateUrl: './carousel.html',
+ styleUrls: ['./carousel.scss'],
+ imports: [GalleryModule]
+})
+export class CarouselExampleComponent {
+ imageIndex = 1;
+ galleryId = 1;
+ autoPlay = true;
+ showArrows = true;
+ showDots = true;
+
+ LIBCONFIG102: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: false
+ }
+ };
+ LIBCONFIG103: CarouselLibConfig = {
+ carouselSlideInfinite: false
+ };
+ LIBCONFIG104: CarouselLibConfig = {
+ carouselDotsConfig: {
+ visible: false
+ }
+ };
+ LIBCONFIG105: CarouselLibConfig = {
+ carouselPlayConfig: {
+ autoPlay: false,
+ interval: 5000,
+ pauseOnHover: true
+ }
+ };
+ LIBCONFIG106: CarouselLibConfig = {
+ carouselPlayConfig: {
+ autoPlay: true,
+ interval: 10000,
+ pauseOnHover: false
+ }
+ };
+ LIBCONFIG107: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 7,
+ width: 'auto',
+ maxHeight: '100px'
+ }
+ };
+ LIBCONFIG113: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5
+ },
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ }
+ };
+ LIBCONFIG114: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '100px'
+ },
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: true
+ }
+ };
+ LIBCONFIG115: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '100px'
+ },
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: false,
+ modalGalleryEnable: false
+ }
+ };
+ LIBCONFIG116: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 7,
+ clickable: false
+ }
+ };
+ LIBCONFIG117: CarouselLibConfig = {
+ carouselImageConfig: {
+ invertSwipe: true
+ }
+ };
+ LIBCONFIG118: CarouselLibConfig = {
+ carouselImageConfig: {
+ description: {
+ strategy: 2
+ }
+ }
+ };
+ LIBCONFIG119: CarouselLibConfig = {
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ },
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '200px'
+ }
+ };
+ LIBCONFIG120: CarouselLibConfig = {
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ },
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '150px'
+ }
+ };
+ LIBCONFIG121: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ breakpoints: {
+ xSmall: 50,
+ small: 60,
+ medium: 80,
+ large: 150,
+ xLarge: 180
+ }
+ }
+ };
+ LIBCONFIG122: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ breakpoints: {
+ xSmall: 50,
+ small: 60,
+ medium: 70,
+ large: 80,
+ xLarge: 100
+ }
+ },
+ carouselConfig: {
+ maxWidth: '500px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ }
+ };
+ LIBCONFIG124: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ breakpoints: {
+ xSmall: 50,
+ small: 60,
+ medium: 70,
+ large: 80,
+ xLarge: 100
+ }
+ },
+ carouselConfig: {
+ maxWidth: '500px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ },
+ carouselPlayConfig: {
+ autoPlay: false,
+ interval: 1,
+ pauseOnHover: true
+ }
+ };
+
+ imagesRect: Image[] = [
+ new Image(
+ 0,
+ {
+ img: '/assets/images/gallery/milan-pegasus-gallery-statue.jpg',
+ description: 'Description 1'
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg',
+ title: 'First image title',
+ alt: 'First image alt',
+ ariaLabel: 'First image aria-label'
+ }
+ ),
+ new Image(1, { img: '/assets/images/gallery/pexels-photo-47223.jpeg' }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-47223.jpg' }),
+ new Image(
+ 2,
+ {
+ img: '/assets/images/gallery/pexels-photo-52062.jpeg',
+ description: 'Description 3',
+ title: 'Third image title',
+ alt: 'Third image alt',
+ ariaLabel: 'Third image aria-label'
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-52062.jpg',
+ description: 'Description 3'
+ }
+ ),
+ new Image(
+ 3,
+ {
+ img: '/assets/images/gallery/pexels-photo-66943.jpeg',
+ description: 'Description 4',
+ title: 'Fourth image title (modal obj)',
+ alt: 'Fourth image alt (modal obj)',
+ ariaLabel: 'Fourth image aria-label (modal obj)'
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-66943.jpg',
+ title: 'Fourth image title (plain obj)',
+ alt: 'Fourth image alt (plain obj)',
+ ariaLabel: 'Fourth image aria-label (plain obj)'
+ }
+ ),
+ new Image(4, { img: '/assets/images/gallery/pexels-photo-93750.jpeg' }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-93750.jpg' }),
+ new Image(
+ 5,
+ {
+ img: '/assets/images/gallery/pexels-photo-94420.jpeg',
+ description: 'Description 6'
+ },
+ { img: '/assets/images/gallery/thumbs/t-pexels-photo-94420.jpg' }
+ ),
+ new Image(6, { img: '/assets/images/gallery/pexels-photo-96947.jpeg' }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-96947.jpg' })
+ ];
+
+ imagesRectWithSources: Image[] = [
+ new Image(
+ 0,
+ {
+ img: '/assets/images/gallery/milan-pegasus-gallery-statue.jpg',
+ description: 'Description 1',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/milan-pegasus-gallery-statue-480w.jpg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/milan-pegasus-gallery-statue-768w.jpg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/milan-pegasus-gallery-statue-1024w.jpg' }
+ ]
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg',
+ title: 'First image title',
+ alt: 'First image alt',
+ ariaLabel: 'First image aria-label'
+ }
+ ),
+ new Image(1, {
+ img: '/assets/images/gallery/pexels-photo-47223.jpeg',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-47223-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-47223-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-47223-1024w.jpeg' }
+ ]
+ }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-47223.jpg' }),
+ new Image(
+ 2,
+ {
+ img: '/assets/images/gallery/pexels-photo-52062.jpeg',
+ description: 'Description 3',
+ title: 'Third image title',
+ alt: 'Third image alt',
+ ariaLabel: 'Third image aria-label',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-52062-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-52062-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-52062-1024w.jpeg' }
+ ]
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-52062.jpg',
+ description: 'Description 3'
+ }
+ ),
+ new Image(
+ 3,
+ {
+ img: '/assets/images/gallery/pexels-photo-66943.jpeg',
+ description: 'Description 4',
+ title: 'Fourth image title (modal obj)',
+ alt: 'Fourth image alt (modal obj)',
+ ariaLabel: 'Fourth image aria-label (modal obj)',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-66943-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-66943-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-66943-1024w.jpeg' }
+ ]
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-66943.jpg',
+ title: 'Fourth image title (plain obj)',
+ alt: 'Fourth image alt (plain obj)',
+ ariaLabel: 'Fourth image aria-label (plain obj)'
+ }
+ ),
+ new Image(4, {
+ img: '/assets/images/gallery/pexels-photo-93750.jpeg',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-93750-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-93750-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-93750-1024w.jpeg' }
+ ]
+ }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-93750.jpg' }),
+ new Image(
+ 5,
+ {
+ img: '/assets/images/gallery/pexels-photo-94420.jpeg',
+ description: 'Description 6',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-94420-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-94420-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-94420-1024w.jpeg' }
+ ]
+ },
+ { img: '/assets/images/gallery/thumbs/t-pexels-photo-94420.jpg' }
+ ),
+ new Image(6, {
+ img: '/assets/images/gallery/pexels-photo-96947.jpeg',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-96947-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-96947-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-96947-1024w.jpeg' }
+ ]
+ }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-96947.jpg' })
+ ];
+
+ emptyImagesArray: Image[] = [];
+
+ imagesRectNoTitles: Image[] = [
+ new Image(
+ 0,
+ { img: '../assets/images/gallery/milan-pegasus-gallery-statue.jpg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg', title: '' }
+ ),
+ new Image(
+ 1,
+ { img: '../assets/images/gallery/pexels-photo-47223.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-47223.jpg', title: '' }
+ ),
+ new Image(
+ 2,
+ { img: '../assets/images/gallery/pexels-photo-52062.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-52062.jpg', title: '' }
+ ),
+ new Image(
+ 3,
+ { img: '../assets/images/gallery/pexels-photo-66943.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-66943.jpg', title: '' }
+ ),
+ new Image(
+ 4,
+ { img: '../assets/images/gallery/pexels-photo-93750.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-93750.jpg', title: '' }
+ ),
+ new Image(
+ 5,
+ { img: '../assets/images/gallery/pexels-photo-94420.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-94420.jpg', title: '' }
+ ),
+ new Image(
+ 6,
+ { img: '../assets/images/gallery/pexels-photo-96947.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-96947.jpg', title: '' }
+ )
+ ];
+
+ fallbackRectImages: Image[] = [
+ new Image(0, {
+ // this file is not available so the browser returns an error
+ img: '../assets/images/gallery/UNEXISTING_IMG1.jpg',
+ // because the img above doesn't exists, the library will use this file
+ fallbackImg: '../assets/images/gallery/fallback-carousel1.jpg'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/UNEXISTING_IMG2.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel2.jpg'
+ }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG3.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel3.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG3.png',
+ fallbackImg: '../assets/images/gallery/fallback-carousel3.jpg'
+ }
+ ),
+ new Image(3, {
+ img: '../assets/images/gallery/UNEXISTING_IMG4.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel4.jpg'
+ }),
+ new Image(
+ 4,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel5.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel5.jpg'
+ }
+ )
+ ];
+
+ accessibilityConfig: AccessibilityConfig = {
+ backgroundAriaLabel: 'CUSTOM Modal gallery full screen background',
+ backgroundTitle: 'CUSTOM background title',
+
+ plainGalleryContentAriaLabel: 'CUSTOM Plain gallery content',
+ plainGalleryContentTitle: 'CUSTOM plain gallery content title',
+
+ modalGalleryContentAriaLabel: 'CUSTOM Modal gallery content',
+ modalGalleryContentTitle: 'CUSTOM modal gallery content title',
+
+ loadingSpinnerAriaLabel: 'CUSTOM The current image is loading. Please be patient.',
+ loadingSpinnerTitle: 'CUSTOM The current image is loading. Please be patient.',
+
+ mainContainerAriaLabel: 'CUSTOM Current image and navigation',
+ mainContainerTitle: 'CUSTOM main container title',
+ mainPrevImageAriaLabel: 'CUSTOM Previous image',
+ mainPrevImageTitle: 'CUSTOM Previous image',
+ mainNextImageAriaLabel: 'CUSTOM Next image',
+ mainNextImageTitle: 'CUSTOM Next image',
+
+ dotsContainerAriaLabel: 'CUSTOM Image navigation dots',
+ dotsContainerTitle: 'CUSTOM dots container title',
+ dotAriaLabel: 'CUSTOM Navigate to image number',
+
+ previewsContainerAriaLabel: 'CUSTOM Image previews',
+ previewsContainerTitle: 'CUSTOM previews title',
+ previewScrollPrevAriaLabel: 'CUSTOM Scroll previous previews',
+ previewScrollPrevTitle: 'CUSTOM Scroll previous previews',
+ previewScrollNextAriaLabel: 'CUSTOM Scroll next previews',
+ previewScrollNextTitle: 'CUSTOM Scroll next previews',
+
+ carouselContainerAriaLabel: 'Current image and navigation',
+ carouselContainerTitle: '',
+ carouselPrevImageAriaLabel: 'Previous image',
+ carouselPrevImageTitle: 'Previous image',
+ carouselNextImageAriaLabel: 'Next image',
+ carouselNextImageTitle: 'Next image',
+ carouselPreviewsContainerAriaLabel: 'Image previews',
+ carouselPreviewsContainerTitle: '',
+ carouselPreviewScrollPrevAriaLabel: 'Scroll previous previews',
+ carouselPreviewScrollPrevTitle: 'Scroll previous previews',
+ carouselPreviewScrollNextAriaLabel: 'Scroll next previews',
+ carouselPreviewScrollNextTitle: 'Scroll next previews'
+ };
+
+ constructor(private modalGalleryService: ModalGalleryService) {
+ }
+
+ onChangeAutoPlay(): void {
+ this.autoPlay = !this.autoPlay;
+ }
+
+ onChangeShowArrows(): void {
+ this.showArrows = !this.showArrows;
+ }
+
+ onChangeShowDots(): void {
+ this.showDots = !this.showDots;
+ }
+
+ // output evets
+ onShow(event: ImageEvent): void {
+ console.log('show', event);
+ }
+
+ onFirstImage(event: ImageEvent): void {
+ console.log('firstImage', event);
+ }
+
+ onLastImage(event: ImageEvent): void {
+ console.log('lastImage', event);
+ }
+
+ getLibConfig108(autoPlay: boolean, showArrows: boolean, showDots: boolean): CarouselLibConfig {
+ return {
+ carouselDotsConfig: {
+ visible: showDots
+ },
+ carouselPlayConfig: {
+ autoPlay: autoPlay,
+ interval: 3000,
+ pauseOnHover: true
+ },
+ carouselConfig: {
+ maxWidth: '100%',
+ maxHeight: '400px',
+ showArrows: showArrows,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ }
+ } as CarouselLibConfig;
+ }
+
+ openModal(imageIndex: number, id: number): void {
+ const imageToShow: Image = this.imagesRect[imageIndex];
+ const dialogRef: ModalGalleryRef = this.modalGalleryService.open({
+ id,
+ images: this.imagesRect,
+ currentImage: imageToShow
+ } as ModalGalleryConfig) as ModalGalleryRef;
+ }
+}
diff --git a/examples/angular-cli-20/src/app/carousel/carousel.html b/examples/angular-cli-20/src/app/carousel/carousel.html
new file mode 100644
index 00000000..5a618f01
--- /dev/null
+++ b/examples/angular-cli-20/src/app/carousel/carousel.html
@@ -0,0 +1,216 @@
+
Carousel
+
+
+
Basic examples
+
+
+
A1 - (id=100) - carousel example (minimal with all defaults) without content projection
+
+
+
+
+
+
A2 - (id=101) - carousel example (minimal with all defaults)
+
+
+
This is my projected content!
+
+
+
+
+
A3 - (id=102) - carousel example without previews
+
+
+
This is my projected content!
+
+
+
+
+
A4 - (id=103) - carousel example without infinite sliding
+
+
+
This is my projected content!
+
+
+
+
+
A5 - (id=104) - carousel example without dots
+
+
+
This is my projected content!
+
+
+
+
+
A6 - (id=105) - carousel example without auto-play (but all other playConfig properties have default values)
+
+
+
This is my projected content!
+
+
+
+
+
A7 - (id=106) - carousel example with a custom playConfig (10s of interval and pauseOnHover disabled)
+
+
+
This is my projected content!
+
+
+
+
+
A8 - (id=107) - carousel example with a custom previewConfig (7 previews with 'auto' width and 100px of height)
+
+
+
This is my projected content!
+
+
+
+
+
A9 - (id=108) - carousel example with buttons to enable/disable autoplay, arrows and other properties
+
Autoplay:
+
Show Arrows:
+
Show Dots:
+
+
+
This is my projected content!
+
+
+
+
+
A10 - (id=109) - carousel example (minimal with all defaults) with outputs
+
+
+
+
+
+
A11 - (id=110) - carousel example with fallback images when it's not possible to load normal images (to fix issue #194)
+
+
+
+
+
+
A12 - (id=111) - carousel example without title attribute on images
+
+
+
+
+
+
A13 - (id=112) - carousel example with an empty images array.
+
Carousel component doesn't support empty images arrays! To prevent runtime errors, you should use an @if to remove the carousel when there are no images.
B1 - (id=113) - carousel example with fixed maxWidth (766px) and custom previews
+
By default, on bigger screen, previews will have height = 200px. If you want you can customize it or also change all breakpoint widths.
+
+
+
This is my projected content!
+
+
+
+
+
B2 - (id=114) - carousel example with fixed maxWidth (766px), custom previews and open modal on click
+
+
+
This is my projected content!
+
+
+
+
+
B3 - (id=115) - carousel example with fixed maxWidth (766px), custom previews and keyboard navigation disabled (for example left/right arrows)
+
+
+
This is my projected content!
+
+
+
+
+
B4 - (id=116) - carousel example with 7 images and unclickable previews
+
+
+
This is my projected content!
+
+
+
+
+
+
Examples with custom current image
+
+
C1 - (id=117) - carousel example with invert swipe on touchscreen devices
+
+
+
This is my projected content!
+
+
+
+
+
C2 - (id=118) - carousel example with description
+
+
+
This is my projected content!
+
+
+
+
+
+
Examples with custom previews height
+
I know that these examples look bad, but the purpose is to show only how the library handles heights.
+ In both examples I didn't set breakpoints, so it will be used default values, but with the maxHeight specified
+
If F1, the maxHeight is 200px, but also the default breakpoints has 200px as maximum size, so the result will be very bad on bigger screen.
+ In F2, I'm using a smaller maxHeight, so previews won't be taller than 150px, despite the default breakpoints on bigger screens (200px).
+
+
F1 - (id=119) - carousel example with fixed maxWidth (766px) and custom previews (maxHeight 200px)
+
+
+
This is my projected content!
+
+
+
+
+
F2 - (id=120) - carousel example with fixed maxWidth (766px) and custom previews (maxHeight 150px)
+
+
+
This is my projected content!
+
+
+
+
+
+
Examples with custom heights for previews (to try responsiveness)
+
+
+
G1 - (id=121) - carousel example (previews with different heights based on the window's width: xSmall: 50, small: 60, medium: 80, large: 150, xLarge: 180)
+
+
+
+
+
+
G2 - (id=122) - carousel example with fixed maxWidth (500px) (previews with different heights based on the window's width: xSmall: 50, small: 60, medium: 70, large: 80, xLarge: 100)
+
+
+
+
+
Examples using sources (to improve LCP)
+
+
+
H1 - (id=123) - carousel example (minimal with all defaults) without content projection - using sources
+
+
+
+
+
H2 - (id=124) - carousel example with fixed maxWidth (500px) (previews with different heights based on the window's width: xSmall: 50, small: 60, medium: 70, large: 80, xLarge: 100) - using sources
+
+
+
+
+
diff --git a/examples/angular-cli-20/src/app/carousel/carousel.scss b/examples/angular-cli-20/src/app/carousel/carousel.scss
new file mode 100644
index 00000000..622bda34
--- /dev/null
+++ b/examples/angular-cli-20/src/app/carousel/carousel.scss
@@ -0,0 +1,157 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+:host {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+}
+
+section {
+ width: 100%;
+}
+
+h2, h3, p {
+ margin-left: 20px;
+}
+
+$text-color: #fff;
+$background: rgba(0, 0, 0, .7);
+
+.my-app-custom-plain-container-row {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+
+ .my-app-custom-image-row {
+ cursor: pointer;
+ height: auto;
+ margin-right: 2px;
+ width: 50px;
+
+ &.after {
+ border-top: 2px;
+ cursor: pointer;
+ display: none;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+ }
+}
+
+.my-app-custom-plain-container-column {
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+
+ .my-app-custom-image-column {
+ cursor: pointer;
+ height: auto;
+ margin-right: 2px;
+ width: 50px;
+
+ &.after {
+ border-top: 2px;
+ cursor: pointer;
+ display: none;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+ }
+}
+
+.my-app-custom-plain-container-with-desc {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+
+ figure {
+ margin: 0;
+ position: relative;
+
+ img {
+ max-width: 100%;
+ height: auto;
+ cursor: pointer;
+ }
+
+ figcaption {
+ background: rgba(0, 0, 0, .5);
+ color: #fff;
+ font-size: 85%;
+ padding: 5px;
+ position: absolute;
+ bottom: 3px;
+ left: 0;
+ right: 0;
+ }
+ }
+
+ .description {
+ font-weight: bold;
+ text-align: center;
+ }
+
+ .my-app-custom-image-with-desc {
+ height: auto;
+ margin-right: 2px;
+ width: 200px;
+ align-self: start;
+ }
+}
+
+.more {
+ background: $background;
+ cursor: pointer;
+ color: $text-color;
+ padding-top: 4px;
+ height: 46px;
+ position: absolute;
+ text-align: center;
+ width: 50px;
+}
+
+
+.projected {
+ color: white;
+ font-weight: 600;
+ font-size: 24px;
+ text-align: center;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ pointer-events: none;
+}
+
+.title {
+ margin-top: 40px;
+}
diff --git a/examples/angular-cli-20/src/app/home/home.component.ts b/examples/angular-cli-20/src/app/home/home.component.ts
new file mode 100644
index 00000000..9d08b6ac
--- /dev/null
+++ b/examples/angular-cli-20/src/app/home/home.component.ts
@@ -0,0 +1,34 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+import { IntroHeaderComponent } from '../intro-header/intro-header.component';
+
+@Component({
+ selector: 'ks-home-page',
+ templateUrl: './home.html',
+ styleUrls: ['./home.scss'],
+ imports: [IntroHeaderComponent]
+})
+export class HomeComponent {}
diff --git a/examples/angular-cli-20/src/app/home/home.html b/examples/angular-cli-20/src/app/home/home.html
new file mode 100644
index 00000000..76dfa3b0
--- /dev/null
+++ b/examples/angular-cli-20/src/app/home/home.html
@@ -0,0 +1,12 @@
+
+
+
+
+
Welcome
+
This is the official live example of @ks89/angular-modal-gallery.
+ To get started quickly you can try and check the sourcecode of this example.
+
+
diff --git a/examples/angular-cli-20/src/app/home/home.scss b/examples/angular-cli-20/src/app/home/home.scss
new file mode 100644
index 00000000..188db17b
--- /dev/null
+++ b/examples/angular-cli-20/src/app/home/home.scss
@@ -0,0 +1,26 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+.container {
+ margin-left: 15px;
+ margin-right: 15px;
+}
diff --git a/examples/angular-cli-20/src/app/intro-header/intro-header.component.ts b/examples/angular-cli-20/src/app/intro-header/intro-header.component.ts
new file mode 100644
index 00000000..2f8069e1
--- /dev/null
+++ b/examples/angular-cli-20/src/app/intro-header/intro-header.component.ts
@@ -0,0 +1,36 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+import { NgOptimizedImage } from '@angular/common';
+
+@Component({
+ selector: 'ks-intro-header',
+ templateUrl: 'intro-header.html',
+ imports: [
+ NgOptimizedImage
+ ],
+ styleUrls: ['intro-header.scss']
+})
+export class IntroHeaderComponent {}
diff --git a/examples/angular-cli-20/src/app/intro-header/intro-header.html b/examples/angular-cli-20/src/app/intro-header/intro-header.html
new file mode 100644
index 00000000..9209ac05
--- /dev/null
+++ b/examples/angular-cli-20/src/app/intro-header/intro-header.html
@@ -0,0 +1,8 @@
+
+
+
@ks89/angular-modal-gallery
+
Image gallery for Angular >=20
+
Currently v14
+
+
diff --git a/examples/angular-cli-20/src/app/intro-header/intro-header.scss b/examples/angular-cli-20/src/app/intro-header/intro-header.scss
new file mode 100644
index 00000000..94e98c84
--- /dev/null
+++ b/examples/angular-cli-20/src/app/intro-header/intro-header.scss
@@ -0,0 +1,73 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// search all occurrences in all code, also html, because it's everywhere
+$color-primary: #101010;
+$color-secondary: #9e9e9e;
+$color-white: #FFF;
+$color-black: #000;
+$color-light-black: #343A40;
+$color-code: #c100e0;
+$color-url: #0060b7;
+$color-warning: #880012;
+
+//$font-huge: 24px;
+$font-big: 18px;
+$font-middle: 14px;
+$font-small: 12px;
+$font-very-small: 10px;
+
+.intro-header {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: center;
+ background-color: $color-primary;
+ background: linear-gradient(135deg, $color-primary, $color-secondary);
+ margin-top: 10px;
+ padding-bottom: 1px;
+ color: $color-white;
+
+ h1 {
+ color: $color-white;
+ font-size: 3rem;
+ }
+}
+
+.project-title {
+ margin-top: 20px;
+ font-size: 2.8rem;
+ text-align: center;
+}
+
+img {
+ margin-top: 25px;
+ border-radius: 10px
+}
+
+.lead {
+ font-size: 1rem;
+ text-align: center;
+ color: #d4d4d4;
+}
diff --git a/examples/angular-cli-20/src/app/modal-gallery/libconfigs.ts b/examples/angular-cli-20/src/app/modal-gallery/libconfigs.ts
new file mode 100644
index 00000000..fdf8c729
--- /dev/null
+++ b/examples/angular-cli-20/src/app/modal-gallery/libconfigs.ts
@@ -0,0 +1,583 @@
+import {
+ ButtonsStrategy,
+ ButtonType,
+ Description,
+ DescriptionStrategy,
+ KS_DEFAULT_BTN_CLOSE,
+ KS_DEFAULT_BTN_DELETE,
+ KS_DEFAULT_BTN_DOWNLOAD,
+ KS_DEFAULT_BTN_EXTURL,
+ KS_DEFAULT_BTN_FULL_SCREEN,
+ LoadingConfig,
+ LoadingType,
+ ModalLibConfig,
+ Size
+} from '@ks89/angular-modal-gallery';
+
+const DEFAULT_SIZE_PREVIEWS: Size = {
+ width: '100px',
+ height: 'auto'
+};
+
+// Examples A
+export const LIBCONFIG_406: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: true,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_407: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_408: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+// Examples B
+export const LIBCONFIG_500: ModalLibConfig = {
+ previewConfig: {
+ visible: false
+ },
+ dotsConfig: {
+ visible: false
+ }
+};
+
+export const LIBCONFIG_501: ModalLibConfig = {
+ buttonsConfig: {
+ visible: false,
+ strategy: ButtonsStrategy.DEFAULT
+ },
+ previewConfig: {
+ visible: false
+ },
+ dotsConfig: {
+ visible: false
+ }
+};
+
+export const LIBCONFIG_502: ModalLibConfig = {
+ buttonsConfig: {
+ visible: false,
+ strategy: ButtonsStrategy.DEFAULT
+ },
+ slideConfig: {
+ infinite: false,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ },
+ previewConfig: {
+ visible: false
+ },
+ dotsConfig: {
+ visible: false
+ }
+};
+
+export const LIBCONFIG_503: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ size: {
+ width: '90px',
+ height: 'auto'
+ }
+ }
+};
+
+export const LIBCONFIG_504: ModalLibConfig = {
+ enableCloseOutside: false
+};
+
+export const LIBCONFIG_505: ModalLibConfig = {
+ currentImageConfig: { downloadable: false },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.DEFAULT
+ }
+};
+
+export const LIBCONFIG_506: ModalLibConfig = {
+ currentImageConfig: { downloadable: true },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.DEFAULT
+ }
+};
+
+export const LIBCONFIG_507: ModalLibConfig = {
+ currentImageConfig: { downloadable: true },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.SIMPLE
+ }
+};
+
+export const LIBCONFIG_508: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_509: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: true,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_510: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: false,
+ type: LoadingType.STANDARD
+ }
+ }
+};
+export const LIBCONFIG_511: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.STANDARD
+ }
+ }
+};
+export const LIBCONFIG_512: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.CIRCULAR
+ }
+ }
+};
+export const LIBCONFIG_513: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.BARS
+ }
+ }
+};
+export const LIBCONFIG_514: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.DOTS
+ }
+ }
+};
+export const LIBCONFIG_515: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.CUBE_FLIPPING
+ }
+ }
+};
+export const LIBCONFIG_516: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.CIRCLES
+ }
+ }
+};
+export const LIBCONFIG_517: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.EXPLOSING_SQUARES
+ }
+ }
+};
+
+export const LIBCONFIG_518: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.DEFAULT
+ }
+};
+export const LIBCONFIG_519: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.SIMPLE
+ }
+};
+export const LIBCONFIG_520: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.ADVANCED
+ }
+};
+export const LIBCONFIG_521: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.FULL
+ }
+};
+
+// default buttons but extUrl will open the link in a new tab instead of the current one
+// this requires to specify all buttons manually (also if they are not really custom)
+export const LIBCONFIG_522: ModalLibConfig = {
+ currentImageConfig: { downloadable: true },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.CUSTOM,
+ buttons: [
+ {
+ className: 'ext-url-image',
+ type: ButtonType.EXTURL,
+ extUrlInNewTab: true // <--- this is the important thing to understand this example
+ },
+ {
+ className: 'download-image',
+ type: ButtonType.DOWNLOAD
+ },
+ {
+ className: 'close-image',
+ type: ButtonType.CLOSE
+ }
+ ]
+ }
+};
+
+export const LIBCONFIG_523: ModalLibConfig = {
+ currentImageConfig: {
+ downloadable: true
+ },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.CUSTOM,
+ buttons: [
+ // KS_DEFAULT_BTN_ROTATE,
+ KS_DEFAULT_BTN_FULL_SCREEN,
+ KS_DEFAULT_BTN_DELETE,
+ KS_DEFAULT_BTN_EXTURL,
+ KS_DEFAULT_BTN_DOWNLOAD,
+ KS_DEFAULT_BTN_CLOSE
+ ]
+ }
+};
+
+export const LIBCONFIG_524: ModalLibConfig = {
+ currentImageConfig: {
+ downloadable: true
+ },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.CUSTOM,
+ buttons: [
+ {
+ className: 'fas fa-plus white',
+ type: ButtonType.CUSTOM,
+ ariaLabel: 'custom plus aria label',
+ title: 'custom plus title',
+ fontSize: '20px'
+ },
+ {
+ className: 'fas fa-times white',
+ type: ButtonType.CLOSE,
+ ariaLabel: 'custom close aria label',
+ title: 'custom close title',
+ fontSize: '20px'
+ },
+ {
+ className: 'fas fa-download white',
+ type: ButtonType.DOWNLOAD,
+ ariaLabel: 'custom download aria label',
+ title: 'custom download title',
+ fontSize: '20px'
+ },
+ {
+ className: 'fas fa-external-link-alt white',
+ type: ButtonType.EXTURL,
+ ariaLabel: 'custom exturl aria label',
+ title: 'custom exturl title',
+ fontSize: '20px'
+ }
+ ]
+ }
+};
+
+export const LIBCONFIG_525: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ mobileVisible: true
+ }
+};
+
+// Examples C
+export const LIBCONFIG_600: ModalLibConfig = {
+ keyboardConfig: {
+ esc: 'KeyQ',
+ left: 'ArrowDown',
+ right: 'ArrowUp'
+ }
+};
+
+export const LIBCONFIG_601: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => '
+ }
+ }
+};
+
+export const LIBCONFIG_602: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.HIDE_IF_EMPTY,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => '
+ }
+ }
+};
+
+export const LIBCONFIG_603: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_HIDDEN,
+ // you should build this value programmatically with the result of (show)="..()" event
+ customFullDescription: 'Custom description of the current visible image'
+ // if customFullDescription !== undefined, all other fields will be ignored
+ // imageText: '',
+ // numberSeparator: '',
+ // beforeTextDescription: '',
+ }
+ }
+};
+
+export const LIBCONFIG_604: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ // you should build this value programmatically with the result of (show)="..()" event
+ customFullDescription: 'Custom description of the current visible image'
+ // if customFullDescription !== undefined, all other fields will be ignored
+ // imageText: '',
+ // numberSeparator: '',
+ // beforeTextDescription: '',
+ }
+ }
+};
+
+export const LIBCONFIG_605: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => '
+ }
+ }
+};
+
+export const LIBCONFIG_606: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => ',
+ style: {
+ bgColor: 'rgba(255,0,0,.5)',
+ textColor: 'blue',
+ marginTop: '3px',
+ marginBottom: '0px',
+ marginLeft: '5px',
+ marginRight: '5px',
+ position: 'absolute',
+ top: '0px',
+ height: '25px'
+ // be careful to use width, in particular with % values
+ }
+ }
+ }
+};
+
+export const LIBCONFIG_607: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ number: 1
+ }
+};
+
+export const LIBCONFIG_608: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ number: 5
+ }
+};
+
+export const LIBCONFIG_609: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ arrows: false
+ }
+};
+
+export const LIBCONFIG_610: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ clickable: false
+ }
+};
+
+export const LIBCONFIG_611: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ size: { width: '30px', height: '30px' }
+ }
+};
+
+export const LIBCONFIG_612: ModalLibConfig = {
+ accessibilityConfig: {
+ backgroundAriaLabel: 'CUSTOM Modal gallery full screen background',
+ backgroundTitle: 'CUSTOM background title',
+
+ plainGalleryContentAriaLabel: 'CUSTOM Plain gallery content',
+ plainGalleryContentTitle: 'CUSTOM plain gallery content title',
+
+ modalGalleryContentAriaLabel: 'CUSTOM Modal gallery content',
+ modalGalleryContentTitle: 'CUSTOM modal gallery content title',
+
+ loadingSpinnerAriaLabel: 'CUSTOM The current image is loading. Please be patient.',
+ loadingSpinnerTitle: 'CUSTOM The current image is loading. Please be patient.',
+
+ mainContainerAriaLabel: 'CUSTOM Current image and navigation',
+ mainContainerTitle: 'CUSTOM main container title',
+ mainPrevImageAriaLabel: 'CUSTOM Previous image',
+ mainPrevImageTitle: 'CUSTOM Previous image',
+ mainNextImageAriaLabel: 'CUSTOM Next image',
+ mainNextImageTitle: 'CUSTOM Next image',
+
+ dotsContainerAriaLabel: 'CUSTOM Image navigation dots',
+ dotsContainerTitle: 'CUSTOM dots container title',
+ dotAriaLabel: 'CUSTOM Navigate to image number',
+
+ previewsContainerAriaLabel: 'CUSTOM Image previews',
+ previewsContainerTitle: 'CUSTOM previews title',
+ previewScrollPrevAriaLabel: 'CUSTOM Scroll previous previews',
+ previewScrollPrevTitle: 'CUSTOM Scroll previous previews',
+ previewScrollNextAriaLabel: 'CUSTOM Scroll next previews',
+ previewScrollNextTitle: 'CUSTOM Scroll next previews',
+
+ carouselContainerAriaLabel: 'Current image and navigation',
+ carouselContainerTitle: '',
+ carouselPrevImageAriaLabel: 'Previous image',
+ carouselPrevImageTitle: 'Previous image',
+ carouselNextImageAriaLabel: 'Next image',
+ carouselNextImageTitle: 'Next image',
+ carouselPreviewsContainerAriaLabel: 'Image previews',
+ carouselPreviewsContainerTitle: '',
+ carouselPreviewScrollPrevAriaLabel: 'Scroll previous previews',
+ carouselPreviewScrollPrevTitle: 'Scroll previous previews',
+ carouselPreviewScrollNextAriaLabel: 'Scroll next previews',
+ carouselPreviewScrollNextTitle: 'Scroll next previews'
+ }
+};
+
+export const LIBCONFIG_613: ModalLibConfig = {
+ currentImageConfig: {
+ navigateOnClick: false
+ }
+};
+
+// Examples D
+export const LIBCONFIG_701: ModalLibConfig = {
+ currentImageConfig: {
+ downloadable: true
+ },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.SIMPLE
+ }
+};
+
+export const LIBCONFIG_702: ModalLibConfig = {
+ currentImageConfig: {
+ invertSwipe: true
+ }
+};
+
+export const LIBCONFIG_703: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: true,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+// Examples E
+export const LIBCONFIG_800: ModalLibConfig = {
+ slideConfig: {
+ playConfig: {
+ autoPlay: true,
+ interval: 5000,
+ pauseOnHover: true
+ }
+ }
+};
+
+export const LIBCONFIG_801: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ playConfig: {
+ autoPlay: true,
+ interval: 5000,
+ pauseOnHover: false
+ }
+ }
+};
+
+export const LIBCONFIG_802: ModalLibConfig = {
+ slideConfig: {
+ playConfig: {
+ autoPlay: true,
+ interval: 1000,
+ pauseOnHover: false
+ }
+ }
+};
+
+// Examples F
+export const LIBCONFIG_900: ModalLibConfig = {
+ slideConfig: {
+ infinite: false
+ },
+ currentImageConfig: {
+ loadingConfig: { enable: true, type: LoadingType.STANDARD } as LoadingConfig,
+ description: { strategy: DescriptionStrategy.ALWAYS_VISIBLE } as Description
+ }
+};
diff --git a/examples/angular-cli-20/src/app/modal-gallery/modal-gallery.component.ts b/examples/angular-cli-20/src/app/modal-gallery/modal-gallery.component.ts
new file mode 100644
index 00000000..716310a4
--- /dev/null
+++ b/examples/angular-cli-20/src/app/modal-gallery/modal-gallery.component.ts
@@ -0,0 +1,765 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { TemplateRef } from '@angular/core';
+import { ViewChild } from '@angular/core';
+import { Component, OnDestroy } from '@angular/core';
+import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
+
+import {
+ Action,
+ ButtonEvent,
+ ButtonType,
+ Image,
+ ImageModalEvent,
+ ModalGalleryService,
+ ModalGalleryRef,
+ ModalGalleryConfig,
+ ModalLibConfig
+} from '@ks89/angular-modal-gallery';
+import { Subscription } from 'rxjs';
+
+import * as libConfigs from './libconfigs';
+import { FormsModule } from '@angular/forms';
+import { NgTemplateOutlet } from '@angular/common';
+
+@Component({
+ selector: 'ks-modal-gallery-page',
+ templateUrl: './modal-gallery.html',
+ styleUrls: ['./modal-gallery.scss'],
+ imports: [FormsModule, NgTemplateOutlet]
+})
+export class ModalGalleryExampleComponent implements OnDestroy {
+ /**
+ * A custom template to illustrate the customization of previews rendering.
+ */
+ @ViewChild('previewsTemplate')
+ previewsTemplate?: TemplateRef;
+
+ imageIndex = 0;
+ galleryId = 1;
+ isPlaying = true;
+ // Examples A
+ CONFIG406: ModalLibConfig = libConfigs.LIBCONFIG_406;
+ CONFIG407: ModalLibConfig = libConfigs.LIBCONFIG_407;
+ CONFIG408: ModalLibConfig = libConfigs.LIBCONFIG_408;
+ // Examples B
+ CONFIG500: ModalLibConfig = libConfigs.LIBCONFIG_500;
+ CONFIG501: ModalLibConfig = libConfigs.LIBCONFIG_501;
+ CONFIG502: ModalLibConfig = libConfigs.LIBCONFIG_502;
+ CONFIG503: ModalLibConfig = libConfigs.LIBCONFIG_503;
+ CONFIG504: ModalLibConfig = libConfigs.LIBCONFIG_504;
+ CONFIG505: ModalLibConfig = libConfigs.LIBCONFIG_505;
+ CONFIG506: ModalLibConfig = libConfigs.LIBCONFIG_506;
+ CONFIG507: ModalLibConfig = libConfigs.LIBCONFIG_507;
+ CONFIG508: ModalLibConfig = libConfigs.LIBCONFIG_508;
+ CONFIG509: ModalLibConfig = libConfigs.LIBCONFIG_509;
+ CONFIG510: ModalLibConfig = libConfigs.LIBCONFIG_510;
+ CONFIG511: ModalLibConfig = libConfigs.LIBCONFIG_511;
+ CONFIG512: ModalLibConfig = libConfigs.LIBCONFIG_512;
+ CONFIG513: ModalLibConfig = libConfigs.LIBCONFIG_513;
+ CONFIG514: ModalLibConfig = libConfigs.LIBCONFIG_514;
+ CONFIG515: ModalLibConfig = libConfigs.LIBCONFIG_515;
+ CONFIG516: ModalLibConfig = libConfigs.LIBCONFIG_516;
+ CONFIG517: ModalLibConfig = libConfigs.LIBCONFIG_517;
+ CONFIG518: ModalLibConfig = libConfigs.LIBCONFIG_518;
+ CONFIG519: ModalLibConfig = libConfigs.LIBCONFIG_519;
+ CONFIG520: ModalLibConfig = libConfigs.LIBCONFIG_520;
+ CONFIG521: ModalLibConfig = libConfigs.LIBCONFIG_521;
+ CONFIG522: ModalLibConfig = libConfigs.LIBCONFIG_522;
+ CONFIG523: ModalLibConfig = libConfigs.LIBCONFIG_523;
+ CONFIG524: ModalLibConfig = libConfigs.LIBCONFIG_524;
+ CONFIG525: ModalLibConfig = libConfigs.LIBCONFIG_525;
+ // Examples C
+ CONFIG600: ModalLibConfig = libConfigs.LIBCONFIG_600;
+ CONFIG601: ModalLibConfig = libConfigs.LIBCONFIG_601;
+ CONFIG602: ModalLibConfig = libConfigs.LIBCONFIG_602;
+ CONFIG603: ModalLibConfig = libConfigs.LIBCONFIG_603;
+ CONFIG604: ModalLibConfig = libConfigs.LIBCONFIG_604;
+ CONFIG605: ModalLibConfig = libConfigs.LIBCONFIG_605;
+ CONFIG606: ModalLibConfig = libConfigs.LIBCONFIG_606;
+ CONFIG607: ModalLibConfig = libConfigs.LIBCONFIG_607;
+ CONFIG608: ModalLibConfig = libConfigs.LIBCONFIG_608;
+ CONFIG609: ModalLibConfig = libConfigs.LIBCONFIG_609;
+ CONFIG610: ModalLibConfig = libConfigs.LIBCONFIG_610;
+ CONFIG611: ModalLibConfig = libConfigs.LIBCONFIG_611;
+ CONFIG612: ModalLibConfig = libConfigs.LIBCONFIG_612;
+ CONFIG613: ModalLibConfig = libConfigs.LIBCONFIG_613;
+ // Examples D
+ CONFIG701: ModalLibConfig = libConfigs.LIBCONFIG_701;
+ CONFIG702: ModalLibConfig = libConfigs.LIBCONFIG_702;
+ CONFIG703: ModalLibConfig = libConfigs.LIBCONFIG_703;
+ // Examples E
+ CONFIG800: ModalLibConfig = libConfigs.LIBCONFIG_800;
+ CONFIG801: ModalLibConfig = libConfigs.LIBCONFIG_801;
+ CONFIG802: ModalLibConfig = libConfigs.LIBCONFIG_802;
+ // Example F
+ CONFIG900: ModalLibConfig = libConfigs.LIBCONFIG_900;
+
+ images: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img2.jpg',
+ description: 'Description 2'
+ }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/img3.jpg',
+ description: 'Description 3',
+ extUrl: 'http://www.google.com'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/img3.png',
+ title: 'custom title 2',
+ alt: 'custom alt 2',
+ ariaLabel: 'arial label 2'
+ }
+ ),
+ new Image(3, {
+ img: '../assets/images/gallery/img4.jpg',
+ description: 'Description 4',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(4, { img: '../assets/images/gallery/img5.jpg' }, { img: '../assets/images/gallery/thumbs/img5.jpg' })
+ ];
+
+ emptyImagesArray: Image[] = [];
+
+ imagesRectNoTitles: Image[] = [
+ new Image(
+ 0,
+ { img: '../assets/images/gallery/milan-pegasus-gallery-statue.jpg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg', title: '' }
+ ),
+ new Image(
+ 1,
+ { img: '../assets/images/gallery/pexels-photo-47223.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-47223.jpg', title: '' }
+ ),
+ new Image(
+ 2,
+ { img: '../assets/images/gallery/pexels-photo-52062.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-52062.jpg', title: '' }
+ ),
+ new Image(
+ 3,
+ { img: '../assets/images/gallery/pexels-photo-66943.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-66943.jpg', title: '' }
+ ),
+ new Image(
+ 4,
+ { img: '../assets/images/gallery/pexels-photo-93750.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-93750.jpg', title: '' }
+ ),
+ new Image(
+ 5,
+ { img: '../assets/images/gallery/pexels-photo-94420.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-94420.jpg', title: '' }
+ ),
+ new Image(
+ 6,
+ { img: '../assets/images/gallery/pexels-photo-96947.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-96947.jpg', title: '' }
+ )
+ ];
+
+ fallbackImages: Image[] = [
+ new Image(0, {
+ // this file is not available so the browser returns an error
+ img: '../assets/images/gallery/UNEXISTING_IMG1.jpg',
+ // because the img above doesn't exists, the library will use this file
+ fallbackImg: '../assets/images/gallery/fallback1.jpg'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/UNEXISTING_IMG2.jpg',
+ fallbackImg: '../assets/images/gallery/fallback2.jpg'
+ }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG3.jpg',
+ fallbackImg: '../assets/images/gallery/fallback3.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG3.png',
+ fallbackImg: '../assets/images/gallery/fallback3.jpg'
+ }
+ ),
+ new Image(3, {
+ img: '../assets/images/gallery/UNEXISTING_IMG4.jpg',
+ fallbackImg: '../assets/images/gallery/fallback4.jpg'
+ }),
+ new Image(
+ 4,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback5.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback5.jpg'
+ }
+ )
+ ];
+
+ // array of images (obviously with different id) where paths are the same.
+ // to prevent caching issues I have to append '?index'.
+ sameImages: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg?1',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img1.jpg?2',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(2, {
+ img: '../assets/images/gallery/img1.jpg?3',
+ extUrl: 'http://www.google.com'
+ })
+ ];
+
+ // example of a png converted into base64 using https://www.base64-image.de/ or other similar websites
+ base64String =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABN0lEQV' +
+ 'R4nO3SQQ2AQBDAwAVlaMEhCkAV' +
+ 'b2RcQmcU9NEZAAAAAOD/tvN675k5VoewxLOvLmAtA8QZIM4AcQaIM0CcAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4gwQZ4A4A8QZIM4AcQaIM0C' +
+ 'cAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4gwQZ4A4A8QZIM4AcQaIM0CcAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4g' +
+ 'wQZ4A4A8QZIM4AcQaIM0CcAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4gwQZ4A4A8QZIM4AcQaIM0CcAeIMEGeAOAPEGQAAAAAA4Pc+8asEoPPGq' +
+ 'xUAAAAASUVORK5CYII';
+
+ base64RedString =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAY1BMVEX/AAD/////WVn/+vr/qan/Nzf/ERH/2tr/s7P/KSn/' +
+ '7+//vr7/0ND/W1v/6+v/m5v/4+P/U1P/HR3/o6P/rq7/g4P/k5P/t7f/dXX/SEj/zMz/ZWX/h4f/bm7/amr/np7/yMhDG/2oAAAC8ElEQVR4nO3dC3KqQBCF4WkHERHFRyKIL/' +
+ 'a/ymDuVYMMFipTbbfnW8H5S4lQVGUMaWe4B3iHQvlQKB8K5UOhfCiUD4XyoVA+FJ7Myijd5dvBO9nmuzQqZ68X2mI9NO9suC7s84VxNuAO6GSQxU8VJvuQe3pn4T55uLDYcK9+' +
+ '0KZ4qDB574vPbej+HF2Fcc499km563p0FAbcQ18QdCi0B+6VLzk0fjtuC0dj7o0vGo/uF064B/agvFcYca/rRdReeOTe1pNjW6HkP6J1gbtQwzV4NnEVJtyrepU0C2M599ldhH' +
+ 'GjcMq9qWfT28KUe1Hv0nrhnHuPB/Na4YJ7jgeLv4UZ9xovsmuhXXKP8WJpL4Ur7i2erC6Fun4Kr8Jz4Rf3Em++/hdKf+htN/5XqOuGtC75LfzmnuHR96nQ6v2SVl9TWxVq/pKevq' +
+ 'aG1twjvFpXhTLeLz1rQMZyb/DMmhH3BM9GRudjxVVmtN51n62M1DdpXeVG2rveR22MxLe9jxgazfdsJ2Oj9en3THsfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
+ 'AAAAAAAAAAAAAAgHba/+98+AFnI+g/30L/GSX6z5nRf1aQ/vOe9J/Zpf/cNf1n533A+Yf6z7DUfw6p/rNkVX9Nkw850/kDzuXWf7Y6ab37Xl0K7ZJ7ixdLeykknQ8YGV0LacG9xo' +
+ 'MF/S2cc8/xYF4rpJR7T+9SqhfSlHtRz6Z0Wxjr+lEM40ahstvThJqFNOFe1aMJuQop4N7Vm4DchXTkXtaTI7UVUsS9rRcRtRequBZLuldII+mPw+MR3S8ke+De+JKDvQ1qFMr+kx' +
+ 'o0cxyFFEt945bHjhpXYXV/I/HN8DBxtrgLiQpp74Y3RUtJW2H1Oe7l3IuHe/fnd7+wuh4zGe+lBpnr+utSWLHF+r0vyeG6aPw+PFT4a1ZG6S7fDt7JNt+lUTnrsL5LoWwolA+F8q' +
+ 'FQPhTKh0L5UCgfCuVDoXw/lnQz7dm7GjoAAAAASUVORK5CYII=';
+ base64GreenString =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIAgMAAADQNkYNAAAADFBMVEUAAAAy/ysy/ysy/ysyTcibAAAAA3RSTlMA2r/af0d' +
+ 'WAAAAQUlEQVRo3u3YMREAMAzEsJAMyZJsMXy3XORdBFySJK3qxFXH1Y1DEARBEARBEARBEARBEARBkNmk436mvSRJ0o4eOKL2P81eyn8AAAAASUVORK5CYII=';
+
+ base64Image: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.base64String);
+ base64RedImage: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.base64RedString);
+ base64GreenImage: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.base64GreenString);
+
+ imagesBase64: Image[] = [
+ new Image(0, {
+ img: this.base64Image,
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: this.base64GreenImage,
+ description: 'Description 2'
+ }),
+ new Image(
+ 2,
+ {
+ img: this.base64RedImage,
+ description: 'Description 3',
+ extUrl: 'http://www.google.com'
+ },
+ {
+ img: this.base64RedImage,
+ title: 'custom title 2',
+ alt: 'custom alt 2',
+ ariaLabel: 'arial label 2'
+ }
+ )
+ ];
+
+ imagesCustomDownloadFileName: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg',
+ downloadFileName: 'first-img.jpg'
+ }),
+ new Image(1, {
+ img: this.base64Image,
+ downloadFileName: 'second-img-base64.jpg'
+ })
+ ];
+
+ imagesHtmlDescriptions: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img2.jpg',
+ description: '
C14 - (id=613) - Advanced demo - disable clicks on current image in modal-image
+
+
+
+
+
C15 - (id=614) - Advanced demo - after 3 seconds it closes modal gallery automatically with galleryService close method
+
Attention: please check the console to understand what it's happening!
+
Timer will clear and restart every time you show another image!
+
+
+
+
+
+
+
Other examples
+
+
D1 - (id=700) - Other demo - base64 images
+
+
+
+
+
D2 - (id=701) - Other demo - custom file name, used when downloaded
+
+
+
+
+
D3 - (id=702) - Other demo - invert touchscreen swipe direction
+
+
+
+
+
D4 - (id=703) - Other demo - infinite sliding and automatic add of images when the gallery is visible
+
+
+
+
+
D5 - (id=704) - Other demo - automatic update of the second image (index=1) via gallery service (open the second image and wait some seconds to see the result)
+
+
+
+
+
D6 - (id=705) - Other demo - remove title attribute on images
+
+
+
+
+
D7 - (id=706) - Other demo - modal gallery with an empty images array
+
+
+
+
+
+
+
AutoPlay examples
+
+
E1 - (id=800) - AutoPlay demo - with interval=5000 and pauseOnHover enabled
+
+
+
+
E2 - (id=801) - AutoPlay demo - with interval=5000, pauseOnHover disabled and infinite is enabled
+
+
+
+
E3 - (id=802) - AutoPlay demo - with interval=1000 and pauseOnHover disabled
+
+
+
+
+
+
+
+
+
Experimental examples*
+
* these will be either improved or removed in next versions
+
+
F1 - (id=900) - Experimental demo - infinite sliding with only one image to see if side arrows are hidden
+
+
+
+
F2 - (id=901) - Experimental demo - an array of Images with the same source file (different classes/ids and paths with appended '?imageIndex' to prevent caching issues)
P9 - (id=208) - row plain gallery layout with fallback images when it's not possible to load normal images (to fix issue #194)
+
+
+
+
+
+
P10 - (id=209) - row plain gallery layout with a tags and custom rectangular sizes (limit 4) + fallback images when it's not possible to load normal images (to fix issue #194)
+
+
+
+
+
+
P11 - (id=210) - row plain gallery layout (limit 2) and custom size + remove title attribute on images
+
+
+
+
+
+
P12 - (id=211) - row plain gallery layout with an empty images array
+
diff --git a/examples/angular-cli-material/src/app/app.component.scss b/examples/angular-cli-material/src/app/app.component.scss
new file mode 100644
index 00000000..2383946c
--- /dev/null
+++ b/examples/angular-cli-material/src/app/app.component.scss
@@ -0,0 +1,47 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+
+#main-title {
+ text-align: center;
+ width: 100%;
+}
+
+#menu {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+
+ > .menu-item {
+ margin-left: 10px;
+ }
+}
+
+.copyright {
+ text-align: center;
+}
+
+.margin {
+ margin-left: 15px;
+ margin-right: 15px;
+}
diff --git a/examples/angular-cli-material/src/app/app.component.ts b/examples/angular-cli-material/src/app/app.component.ts
new file mode 100644
index 00000000..3ce54d74
--- /dev/null
+++ b/examples/angular-cli-material/src/app/app.component.ts
@@ -0,0 +1,111 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Component, Inject } from '@angular/core';
+import { MatDialog, MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
+import { MatButtonModule } from '@angular/material/button';
+import { MatGridListModule } from '@angular/material/grid-list';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { RouterOutlet } from '@angular/router';
+
+import { NavbarComponent } from './navbar/navbar.component';
+
+export interface DialogData {
+ animal: string;
+ name: string;
+}
+
+@Component({
+ selector: 'ks-root',
+ templateUrl: 'app.component.html',
+ styleUrls: ['app.component.scss'],
+ imports: [
+ MatButtonModule,
+ MatGridListModule,
+ MatDialogModule,
+ MatFormFieldModule,
+ MatInputModule,
+ FormsModule,
+ ReactiveFormsModule,
+ RouterOutlet,
+ NavbarComponent,
+ ]
+})
+export class AppComponent {
+ // ----------------------------------------------------
+ // ----------------------------------------------------
+ tiles = [
+ { text: 'One', cols: 3, rows: 1, color: 'lightblue' },
+ { text: 'Two', cols: 1, rows: 2, color: 'lightgreen' },
+ { text: 'Three', cols: 1, rows: 1, color: 'lightpink' },
+ { text: 'Four', cols: 2, rows: 1, color: '#DDBDF1' }
+ ];
+
+ animal: string | undefined;
+ name: string | undefined;
+
+ constructor(public dialog: MatDialog) {
+ }
+
+ openDialog(): void {
+ const dialogRef = this.dialog.open(DialogOverviewExampleDialog, {
+ width: '250px',
+ data: { name: this.name, animal: this.animal }
+ });
+
+ dialogRef.afterClosed().subscribe((result: any) => {
+ console.log('The dialog was closed');
+ this.animal = result;
+ });
+ }
+}
+
+// ----------------------------------------------------
+// ----------------------------------------------------
+@Component({
+ selector: 'dialog-overview-example-dialog',
+ templateUrl: 'dialog-overview-example-dialog.html',
+ imports: [
+ MatButtonModule,
+ MatGridListModule,
+ MatDialogModule,
+ MatFormFieldModule,
+ MatInputModule,
+ FormsModule
+ ]
+})
+export class DialogOverviewExampleDialog {
+ constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: DialogData) {
+ }
+
+ onNoClick(): void {
+ this.dialogRef.close();
+ }
+}
+
+// ----------------------------------------------------
+// ----------------------------------------------------
+
diff --git a/examples/angular-cli-material/src/app/app.config.ts b/examples/angular-cli-material/src/app/app.config.ts
new file mode 100644
index 00000000..563e35ae
--- /dev/null
+++ b/examples/angular-cli-material/src/app/app.config.ts
@@ -0,0 +1,23 @@
+import { ApplicationConfig, importProvidersFrom, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
+import { provideRouter } from '@angular/router';
+import { BrowserModule } from '@angular/platform-browser';
+import { FormsModule } from '@angular/forms';
+
+import { GalleryModule } from '@ks89/angular-modal-gallery';
+
+import { routes } from './app.routes';
+
+export const appConfig: ApplicationConfig = {
+ providers: [
+ provideBrowserGlobalErrorListeners(),
+ provideZoneChangeDetection({ eventCoalescing: true }),
+ provideRouter(routes),
+ importProvidersFrom(
+ BrowserModule,
+ FormsModule,
+
+ // import GalleryModule. Install @ks89/angular-modal-gallery first
+ GalleryModule
+ )
+ ]
+};
diff --git a/examples/angular-cli-material/src/app/app.routes.ts b/examples/angular-cli-material/src/app/app.routes.ts
new file mode 100644
index 00000000..f91e522a
--- /dev/null
+++ b/examples/angular-cli-material/src/app/app.routes.ts
@@ -0,0 +1,13 @@
+import { Routes } from '@angular/router';
+
+import { ModalGalleryExampleComponent } from './modal-gallery/modal-gallery.component';
+import { PlainGalleryExampleComponent } from './plain-gallery/plain-gallery.component';
+import { CarouselExampleComponent } from './carousel/carousel.component';
+import { HomeComponent } from './home/home.component';
+
+export const routes: Routes = [
+ { path: '', component: HomeComponent },
+ { path: 'carousel', component: CarouselExampleComponent },
+ { path: 'modal', component: ModalGalleryExampleComponent },
+ { path: 'plain', component: PlainGalleryExampleComponent }
+];
diff --git a/examples/angular-cli-material/src/app/carousel/carousel.component.ts b/examples/angular-cli-material/src/app/carousel/carousel.component.ts
new file mode 100644
index 00000000..90cb52b0
--- /dev/null
+++ b/examples/angular-cli-material/src/app/carousel/carousel.component.ts
@@ -0,0 +1,571 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+
+import {
+ AccessibilityConfig, CarouselLibConfig, Image, ImageEvent,
+ ModalGalleryConfig, ModalGalleryRef, ModalGalleryService, GalleryModule
+} from '@ks89/angular-modal-gallery';
+
+@Component({
+ selector: 'ks-carousel-page',
+ templateUrl: './carousel.html',
+ styleUrls: ['./carousel.scss'],
+ imports: [GalleryModule]
+})
+export class CarouselExampleComponent {
+ imageIndex = 1;
+ galleryId = 1;
+ autoPlay = true;
+ showArrows = true;
+ showDots = true;
+
+ LIBCONFIG102: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: false
+ }
+ };
+ LIBCONFIG103: CarouselLibConfig = {
+ carouselSlideInfinite: false
+ };
+ LIBCONFIG104: CarouselLibConfig = {
+ carouselDotsConfig: {
+ visible: false
+ }
+ };
+ LIBCONFIG105: CarouselLibConfig = {
+ carouselPlayConfig: {
+ autoPlay: false,
+ interval: 5000,
+ pauseOnHover: true
+ }
+ };
+ LIBCONFIG106: CarouselLibConfig = {
+ carouselPlayConfig: {
+ autoPlay: true,
+ interval: 10000,
+ pauseOnHover: false
+ }
+ };
+ LIBCONFIG107: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 7,
+ width: 'auto',
+ maxHeight: '100px'
+ }
+ };
+ LIBCONFIG113: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5
+ },
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ }
+ };
+ LIBCONFIG114: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '100px'
+ },
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: true
+ }
+ };
+ LIBCONFIG115: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '100px'
+ },
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: false,
+ modalGalleryEnable: false
+ }
+ };
+ LIBCONFIG116: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 7,
+ clickable: false
+ }
+ };
+ LIBCONFIG117: CarouselLibConfig = {
+ carouselImageConfig: {
+ invertSwipe: true
+ }
+ };
+ LIBCONFIG118: CarouselLibConfig = {
+ carouselImageConfig: {
+ description: {
+ strategy: 2
+ }
+ }
+ };
+ LIBCONFIG119: CarouselLibConfig = {
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ },
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '200px'
+ }
+ };
+ LIBCONFIG120: CarouselLibConfig = {
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ },
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '150px'
+ }
+ };
+ LIBCONFIG121: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ breakpoints: {
+ xSmall: 50,
+ small: 60,
+ medium: 80,
+ large: 150,
+ xLarge: 180
+ }
+ }
+ };
+ LIBCONFIG122: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ breakpoints: {
+ xSmall: 50,
+ small: 60,
+ medium: 70,
+ large: 80,
+ xLarge: 100
+ }
+ },
+ carouselConfig: {
+ maxWidth: '500px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ }
+ };
+ LIBCONFIG124: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ breakpoints: {
+ xSmall: 50,
+ small: 60,
+ medium: 70,
+ large: 80,
+ xLarge: 100
+ }
+ },
+ carouselConfig: {
+ maxWidth: '500px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ },
+ carouselPlayConfig: {
+ autoPlay: false,
+ interval: 1,
+ pauseOnHover: true
+ }
+ };
+
+ imagesRect: Image[] = [
+ new Image(
+ 0,
+ {
+ img: '/assets/images/gallery/milan-pegasus-gallery-statue.jpg',
+ description: 'Description 1'
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg',
+ title: 'First image title',
+ alt: 'First image alt',
+ ariaLabel: 'First image aria-label'
+ }
+ ),
+ new Image(1, { img: '/assets/images/gallery/pexels-photo-47223.jpeg' }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-47223.jpg' }),
+ new Image(
+ 2,
+ {
+ img: '/assets/images/gallery/pexels-photo-52062.jpeg',
+ description: 'Description 3',
+ title: 'Third image title',
+ alt: 'Third image alt',
+ ariaLabel: 'Third image aria-label'
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-52062.jpg',
+ description: 'Description 3'
+ }
+ ),
+ new Image(
+ 3,
+ {
+ img: '/assets/images/gallery/pexels-photo-66943.jpeg',
+ description: 'Description 4',
+ title: 'Fourth image title (modal obj)',
+ alt: 'Fourth image alt (modal obj)',
+ ariaLabel: 'Fourth image aria-label (modal obj)'
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-66943.jpg',
+ title: 'Fourth image title (plain obj)',
+ alt: 'Fourth image alt (plain obj)',
+ ariaLabel: 'Fourth image aria-label (plain obj)'
+ }
+ ),
+ new Image(4, { img: '/assets/images/gallery/pexels-photo-93750.jpeg' }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-93750.jpg' }),
+ new Image(
+ 5,
+ {
+ img: '/assets/images/gallery/pexels-photo-94420.jpeg',
+ description: 'Description 6'
+ },
+ { img: '/assets/images/gallery/thumbs/t-pexels-photo-94420.jpg' }
+ ),
+ new Image(6, { img: '/assets/images/gallery/pexels-photo-96947.jpeg' }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-96947.jpg' })
+ ];
+
+ imagesRectWithSources: Image[] = [
+ new Image(
+ 0,
+ {
+ img: '/assets/images/gallery/milan-pegasus-gallery-statue.jpg',
+ description: 'Description 1',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/milan-pegasus-gallery-statue-480w.jpg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/milan-pegasus-gallery-statue-768w.jpg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/milan-pegasus-gallery-statue-1024w.jpg' }
+ ]
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg',
+ title: 'First image title',
+ alt: 'First image alt',
+ ariaLabel: 'First image aria-label'
+ }
+ ),
+ new Image(1, {
+ img: '/assets/images/gallery/pexels-photo-47223.jpeg',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-47223-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-47223-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-47223-1024w.jpeg' }
+ ]
+ }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-47223.jpg' }),
+ new Image(
+ 2,
+ {
+ img: '/assets/images/gallery/pexels-photo-52062.jpeg',
+ description: 'Description 3',
+ title: 'Third image title',
+ alt: 'Third image alt',
+ ariaLabel: 'Third image aria-label',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-52062-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-52062-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-52062-1024w.jpeg' }
+ ]
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-52062.jpg',
+ description: 'Description 3'
+ }
+ ),
+ new Image(
+ 3,
+ {
+ img: '/assets/images/gallery/pexels-photo-66943.jpeg',
+ description: 'Description 4',
+ title: 'Fourth image title (modal obj)',
+ alt: 'Fourth image alt (modal obj)',
+ ariaLabel: 'Fourth image aria-label (modal obj)',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-66943-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-66943-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-66943-1024w.jpeg' }
+ ]
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-66943.jpg',
+ title: 'Fourth image title (plain obj)',
+ alt: 'Fourth image alt (plain obj)',
+ ariaLabel: 'Fourth image aria-label (plain obj)'
+ }
+ ),
+ new Image(4, {
+ img: '/assets/images/gallery/pexels-photo-93750.jpeg',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-93750-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-93750-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-93750-1024w.jpeg' }
+ ]
+ }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-93750.jpg' }),
+ new Image(
+ 5,
+ {
+ img: '/assets/images/gallery/pexels-photo-94420.jpeg',
+ description: 'Description 6',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-94420-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-94420-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-94420-1024w.jpeg' }
+ ]
+ },
+ { img: '/assets/images/gallery/thumbs/t-pexels-photo-94420.jpg' }
+ ),
+ new Image(6, {
+ img: '/assets/images/gallery/pexels-photo-96947.jpeg',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-96947-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-96947-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-96947-1024w.jpeg' }
+ ]
+ }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-96947.jpg' })
+ ];
+
+ emptyImagesArray: Image[] = [];
+
+ imagesRectNoTitles: Image[] = [
+ new Image(
+ 0,
+ { img: '../assets/images/gallery/milan-pegasus-gallery-statue.jpg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg', title: '' }
+ ),
+ new Image(
+ 1,
+ { img: '../assets/images/gallery/pexels-photo-47223.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-47223.jpg', title: '' }
+ ),
+ new Image(
+ 2,
+ { img: '../assets/images/gallery/pexels-photo-52062.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-52062.jpg', title: '' }
+ ),
+ new Image(
+ 3,
+ { img: '../assets/images/gallery/pexels-photo-66943.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-66943.jpg', title: '' }
+ ),
+ new Image(
+ 4,
+ { img: '../assets/images/gallery/pexels-photo-93750.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-93750.jpg', title: '' }
+ ),
+ new Image(
+ 5,
+ { img: '../assets/images/gallery/pexels-photo-94420.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-94420.jpg', title: '' }
+ ),
+ new Image(
+ 6,
+ { img: '../assets/images/gallery/pexels-photo-96947.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-96947.jpg', title: '' }
+ )
+ ];
+
+ fallbackRectImages: Image[] = [
+ new Image(0, {
+ // this file is not available so the browser returns an error
+ img: '../assets/images/gallery/UNEXISTING_IMG1.jpg',
+ // because the img above doesn't exists, the library will use this file
+ fallbackImg: '../assets/images/gallery/fallback-carousel1.jpg'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/UNEXISTING_IMG2.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel2.jpg'
+ }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG3.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel3.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG3.png',
+ fallbackImg: '../assets/images/gallery/fallback-carousel3.jpg'
+ }
+ ),
+ new Image(3, {
+ img: '../assets/images/gallery/UNEXISTING_IMG4.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel4.jpg'
+ }),
+ new Image(
+ 4,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel5.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel5.jpg'
+ }
+ )
+ ];
+
+ accessibilityConfig: AccessibilityConfig = {
+ backgroundAriaLabel: 'CUSTOM Modal gallery full screen background',
+ backgroundTitle: 'CUSTOM background title',
+
+ plainGalleryContentAriaLabel: 'CUSTOM Plain gallery content',
+ plainGalleryContentTitle: 'CUSTOM plain gallery content title',
+
+ modalGalleryContentAriaLabel: 'CUSTOM Modal gallery content',
+ modalGalleryContentTitle: 'CUSTOM modal gallery content title',
+
+ loadingSpinnerAriaLabel: 'CUSTOM The current image is loading. Please be patient.',
+ loadingSpinnerTitle: 'CUSTOM The current image is loading. Please be patient.',
+
+ mainContainerAriaLabel: 'CUSTOM Current image and navigation',
+ mainContainerTitle: 'CUSTOM main container title',
+ mainPrevImageAriaLabel: 'CUSTOM Previous image',
+ mainPrevImageTitle: 'CUSTOM Previous image',
+ mainNextImageAriaLabel: 'CUSTOM Next image',
+ mainNextImageTitle: 'CUSTOM Next image',
+
+ dotsContainerAriaLabel: 'CUSTOM Image navigation dots',
+ dotsContainerTitle: 'CUSTOM dots container title',
+ dotAriaLabel: 'CUSTOM Navigate to image number',
+
+ previewsContainerAriaLabel: 'CUSTOM Image previews',
+ previewsContainerTitle: 'CUSTOM previews title',
+ previewScrollPrevAriaLabel: 'CUSTOM Scroll previous previews',
+ previewScrollPrevTitle: 'CUSTOM Scroll previous previews',
+ previewScrollNextAriaLabel: 'CUSTOM Scroll next previews',
+ previewScrollNextTitle: 'CUSTOM Scroll next previews',
+
+ carouselContainerAriaLabel: 'Current image and navigation',
+ carouselContainerTitle: '',
+ carouselPrevImageAriaLabel: 'Previous image',
+ carouselPrevImageTitle: 'Previous image',
+ carouselNextImageAriaLabel: 'Next image',
+ carouselNextImageTitle: 'Next image',
+ carouselPreviewsContainerAriaLabel: 'Image previews',
+ carouselPreviewsContainerTitle: '',
+ carouselPreviewScrollPrevAriaLabel: 'Scroll previous previews',
+ carouselPreviewScrollPrevTitle: 'Scroll previous previews',
+ carouselPreviewScrollNextAriaLabel: 'Scroll next previews',
+ carouselPreviewScrollNextTitle: 'Scroll next previews'
+ };
+
+ constructor(private modalGalleryService: ModalGalleryService) {
+ }
+
+ onChangeAutoPlay(): void {
+ this.autoPlay = !this.autoPlay;
+ }
+
+ onChangeShowArrows(): void {
+ this.showArrows = !this.showArrows;
+ }
+
+ onChangeShowDots(): void {
+ this.showDots = !this.showDots;
+ }
+
+ // output evets
+ onShow(event: ImageEvent): void {
+ console.log('show', event);
+ }
+
+ onFirstImage(event: ImageEvent): void {
+ console.log('firstImage', event);
+ }
+
+ onLastImage(event: ImageEvent): void {
+ console.log('lastImage', event);
+ }
+
+ getLibConfig108(autoPlay: boolean, showArrows: boolean, showDots: boolean): CarouselLibConfig {
+ return {
+ carouselDotsConfig: {
+ visible: showDots
+ },
+ carouselPlayConfig: {
+ autoPlay: autoPlay,
+ interval: 3000,
+ pauseOnHover: true
+ },
+ carouselConfig: {
+ maxWidth: '100%',
+ maxHeight: '400px',
+ showArrows: showArrows,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ }
+ } as CarouselLibConfig;
+ }
+
+ openModal(imageIndex: number, id: number): void {
+ const imageToShow: Image = this.imagesRect[imageIndex];
+ const dialogRef: ModalGalleryRef = this.modalGalleryService.open({
+ id,
+ images: this.imagesRect,
+ currentImage: imageToShow
+ } as ModalGalleryConfig) as ModalGalleryRef;
+ }
+}
diff --git a/examples/angular-cli-material/src/app/carousel/carousel.html b/examples/angular-cli-material/src/app/carousel/carousel.html
new file mode 100644
index 00000000..5a618f01
--- /dev/null
+++ b/examples/angular-cli-material/src/app/carousel/carousel.html
@@ -0,0 +1,216 @@
+
Carousel
+
+
+
Basic examples
+
+
+
A1 - (id=100) - carousel example (minimal with all defaults) without content projection
+
+
+
+
+
+
A2 - (id=101) - carousel example (minimal with all defaults)
+
+
+
This is my projected content!
+
+
+
+
+
A3 - (id=102) - carousel example without previews
+
+
+
This is my projected content!
+
+
+
+
+
A4 - (id=103) - carousel example without infinite sliding
+
+
+
This is my projected content!
+
+
+
+
+
A5 - (id=104) - carousel example without dots
+
+
+
This is my projected content!
+
+
+
+
+
A6 - (id=105) - carousel example without auto-play (but all other playConfig properties have default values)
+
+
+
This is my projected content!
+
+
+
+
+
A7 - (id=106) - carousel example with a custom playConfig (10s of interval and pauseOnHover disabled)
+
+
+
This is my projected content!
+
+
+
+
+
A8 - (id=107) - carousel example with a custom previewConfig (7 previews with 'auto' width and 100px of height)
+
+
+
This is my projected content!
+
+
+
+
+
A9 - (id=108) - carousel example with buttons to enable/disable autoplay, arrows and other properties
+
Autoplay:
+
Show Arrows:
+
Show Dots:
+
+
+
This is my projected content!
+
+
+
+
+
A10 - (id=109) - carousel example (minimal with all defaults) with outputs
+
+
+
+
+
+
A11 - (id=110) - carousel example with fallback images when it's not possible to load normal images (to fix issue #194)
+
+
+
+
+
+
A12 - (id=111) - carousel example without title attribute on images
+
+
+
+
+
+
A13 - (id=112) - carousel example with an empty images array.
+
Carousel component doesn't support empty images arrays! To prevent runtime errors, you should use an @if to remove the carousel when there are no images.
B1 - (id=113) - carousel example with fixed maxWidth (766px) and custom previews
+
By default, on bigger screen, previews will have height = 200px. If you want you can customize it or also change all breakpoint widths.
+
+
+
This is my projected content!
+
+
+
+
+
B2 - (id=114) - carousel example with fixed maxWidth (766px), custom previews and open modal on click
+
+
+
This is my projected content!
+
+
+
+
+
B3 - (id=115) - carousel example with fixed maxWidth (766px), custom previews and keyboard navigation disabled (for example left/right arrows)
+
+
+
This is my projected content!
+
+
+
+
+
B4 - (id=116) - carousel example with 7 images and unclickable previews
+
+
+
This is my projected content!
+
+
+
+
+
+
Examples with custom current image
+
+
C1 - (id=117) - carousel example with invert swipe on touchscreen devices
+
+
+
This is my projected content!
+
+
+
+
+
C2 - (id=118) - carousel example with description
+
+
+
This is my projected content!
+
+
+
+
+
+
Examples with custom previews height
+
I know that these examples look bad, but the purpose is to show only how the library handles heights.
+ In both examples I didn't set breakpoints, so it will be used default values, but with the maxHeight specified
+
If F1, the maxHeight is 200px, but also the default breakpoints has 200px as maximum size, so the result will be very bad on bigger screen.
+ In F2, I'm using a smaller maxHeight, so previews won't be taller than 150px, despite the default breakpoints on bigger screens (200px).
+
+
F1 - (id=119) - carousel example with fixed maxWidth (766px) and custom previews (maxHeight 200px)
+
+
+
This is my projected content!
+
+
+
+
+
F2 - (id=120) - carousel example with fixed maxWidth (766px) and custom previews (maxHeight 150px)
+
+
+
This is my projected content!
+
+
+
+
+
+
Examples with custom heights for previews (to try responsiveness)
+
+
+
G1 - (id=121) - carousel example (previews with different heights based on the window's width: xSmall: 50, small: 60, medium: 80, large: 150, xLarge: 180)
+
+
+
+
+
+
G2 - (id=122) - carousel example with fixed maxWidth (500px) (previews with different heights based on the window's width: xSmall: 50, small: 60, medium: 70, large: 80, xLarge: 100)
+
+
+
+
+
Examples using sources (to improve LCP)
+
+
+
H1 - (id=123) - carousel example (minimal with all defaults) without content projection - using sources
+
+
+
+
+
H2 - (id=124) - carousel example with fixed maxWidth (500px) (previews with different heights based on the window's width: xSmall: 50, small: 60, medium: 70, large: 80, xLarge: 100) - using sources
+
+
+
+
+
diff --git a/examples/angular-cli-material/src/app/carousel/carousel.scss b/examples/angular-cli-material/src/app/carousel/carousel.scss
new file mode 100644
index 00000000..622bda34
--- /dev/null
+++ b/examples/angular-cli-material/src/app/carousel/carousel.scss
@@ -0,0 +1,157 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+:host {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+}
+
+section {
+ width: 100%;
+}
+
+h2, h3, p {
+ margin-left: 20px;
+}
+
+$text-color: #fff;
+$background: rgba(0, 0, 0, .7);
+
+.my-app-custom-plain-container-row {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+
+ .my-app-custom-image-row {
+ cursor: pointer;
+ height: auto;
+ margin-right: 2px;
+ width: 50px;
+
+ &.after {
+ border-top: 2px;
+ cursor: pointer;
+ display: none;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+ }
+}
+
+.my-app-custom-plain-container-column {
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+
+ .my-app-custom-image-column {
+ cursor: pointer;
+ height: auto;
+ margin-right: 2px;
+ width: 50px;
+
+ &.after {
+ border-top: 2px;
+ cursor: pointer;
+ display: none;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+ }
+}
+
+.my-app-custom-plain-container-with-desc {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+
+ figure {
+ margin: 0;
+ position: relative;
+
+ img {
+ max-width: 100%;
+ height: auto;
+ cursor: pointer;
+ }
+
+ figcaption {
+ background: rgba(0, 0, 0, .5);
+ color: #fff;
+ font-size: 85%;
+ padding: 5px;
+ position: absolute;
+ bottom: 3px;
+ left: 0;
+ right: 0;
+ }
+ }
+
+ .description {
+ font-weight: bold;
+ text-align: center;
+ }
+
+ .my-app-custom-image-with-desc {
+ height: auto;
+ margin-right: 2px;
+ width: 200px;
+ align-self: start;
+ }
+}
+
+.more {
+ background: $background;
+ cursor: pointer;
+ color: $text-color;
+ padding-top: 4px;
+ height: 46px;
+ position: absolute;
+ text-align: center;
+ width: 50px;
+}
+
+
+.projected {
+ color: white;
+ font-weight: 600;
+ font-size: 24px;
+ text-align: center;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ pointer-events: none;
+}
+
+.title {
+ margin-top: 40px;
+}
diff --git a/examples/angular-cli-material/src/app/dialog-overview-example-dialog.html b/examples/angular-cli-material/src/app/dialog-overview-example-dialog.html
new file mode 100644
index 00000000..3cff5c92
--- /dev/null
+++ b/examples/angular-cli-material/src/app/dialog-overview-example-dialog.html
@@ -0,0 +1,16 @@
+
Hi {{data.name}}
+
+
What's your favorite animal?
+
+ Favorite Animal
+
+
+
+
+
+
+
+
+
diff --git a/examples/angular-cli-material/src/app/home/home.component.ts b/examples/angular-cli-material/src/app/home/home.component.ts
new file mode 100644
index 00000000..9d08b6ac
--- /dev/null
+++ b/examples/angular-cli-material/src/app/home/home.component.ts
@@ -0,0 +1,34 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+import { IntroHeaderComponent } from '../intro-header/intro-header.component';
+
+@Component({
+ selector: 'ks-home-page',
+ templateUrl: './home.html',
+ styleUrls: ['./home.scss'],
+ imports: [IntroHeaderComponent]
+})
+export class HomeComponent {}
diff --git a/examples/angular-cli-material/src/app/home/home.html b/examples/angular-cli-material/src/app/home/home.html
new file mode 100644
index 00000000..76dfa3b0
--- /dev/null
+++ b/examples/angular-cli-material/src/app/home/home.html
@@ -0,0 +1,12 @@
+
+
+
+
+
Welcome
+
This is the official live example of @ks89/angular-modal-gallery.
+ To get started quickly you can try and check the sourcecode of this example.
+
+
diff --git a/examples/angular-cli-material/src/app/home/home.scss b/examples/angular-cli-material/src/app/home/home.scss
new file mode 100644
index 00000000..188db17b
--- /dev/null
+++ b/examples/angular-cli-material/src/app/home/home.scss
@@ -0,0 +1,26 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+.container {
+ margin-left: 15px;
+ margin-right: 15px;
+}
diff --git a/examples/angular-cli-material/src/app/intro-header/intro-header.component.ts b/examples/angular-cli-material/src/app/intro-header/intro-header.component.ts
new file mode 100644
index 00000000..2f8069e1
--- /dev/null
+++ b/examples/angular-cli-material/src/app/intro-header/intro-header.component.ts
@@ -0,0 +1,36 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+import { NgOptimizedImage } from '@angular/common';
+
+@Component({
+ selector: 'ks-intro-header',
+ templateUrl: 'intro-header.html',
+ imports: [
+ NgOptimizedImage
+ ],
+ styleUrls: ['intro-header.scss']
+})
+export class IntroHeaderComponent {}
diff --git a/examples/angular-cli-material/src/app/intro-header/intro-header.html b/examples/angular-cli-material/src/app/intro-header/intro-header.html
new file mode 100644
index 00000000..9209ac05
--- /dev/null
+++ b/examples/angular-cli-material/src/app/intro-header/intro-header.html
@@ -0,0 +1,8 @@
+
+
+
@ks89/angular-modal-gallery
+
Image gallery for Angular >=20
+
Currently v14
+
+
diff --git a/examples/angular-cli-material/src/app/intro-header/intro-header.scss b/examples/angular-cli-material/src/app/intro-header/intro-header.scss
new file mode 100644
index 00000000..94e98c84
--- /dev/null
+++ b/examples/angular-cli-material/src/app/intro-header/intro-header.scss
@@ -0,0 +1,73 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// search all occurrences in all code, also html, because it's everywhere
+$color-primary: #101010;
+$color-secondary: #9e9e9e;
+$color-white: #FFF;
+$color-black: #000;
+$color-light-black: #343A40;
+$color-code: #c100e0;
+$color-url: #0060b7;
+$color-warning: #880012;
+
+//$font-huge: 24px;
+$font-big: 18px;
+$font-middle: 14px;
+$font-small: 12px;
+$font-very-small: 10px;
+
+.intro-header {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: center;
+ background-color: $color-primary;
+ background: linear-gradient(135deg, $color-primary, $color-secondary);
+ margin-top: 10px;
+ padding-bottom: 1px;
+ color: $color-white;
+
+ h1 {
+ color: $color-white;
+ font-size: 3rem;
+ }
+}
+
+.project-title {
+ margin-top: 20px;
+ font-size: 2.8rem;
+ text-align: center;
+}
+
+img {
+ margin-top: 25px;
+ border-radius: 10px
+}
+
+.lead {
+ font-size: 1rem;
+ text-align: center;
+ color: #d4d4d4;
+}
diff --git a/examples/angular-cli-material/src/app/modal-gallery/libconfigs.ts b/examples/angular-cli-material/src/app/modal-gallery/libconfigs.ts
new file mode 100644
index 00000000..fdf8c729
--- /dev/null
+++ b/examples/angular-cli-material/src/app/modal-gallery/libconfigs.ts
@@ -0,0 +1,583 @@
+import {
+ ButtonsStrategy,
+ ButtonType,
+ Description,
+ DescriptionStrategy,
+ KS_DEFAULT_BTN_CLOSE,
+ KS_DEFAULT_BTN_DELETE,
+ KS_DEFAULT_BTN_DOWNLOAD,
+ KS_DEFAULT_BTN_EXTURL,
+ KS_DEFAULT_BTN_FULL_SCREEN,
+ LoadingConfig,
+ LoadingType,
+ ModalLibConfig,
+ Size
+} from '@ks89/angular-modal-gallery';
+
+const DEFAULT_SIZE_PREVIEWS: Size = {
+ width: '100px',
+ height: 'auto'
+};
+
+// Examples A
+export const LIBCONFIG_406: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: true,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_407: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_408: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+// Examples B
+export const LIBCONFIG_500: ModalLibConfig = {
+ previewConfig: {
+ visible: false
+ },
+ dotsConfig: {
+ visible: false
+ }
+};
+
+export const LIBCONFIG_501: ModalLibConfig = {
+ buttonsConfig: {
+ visible: false,
+ strategy: ButtonsStrategy.DEFAULT
+ },
+ previewConfig: {
+ visible: false
+ },
+ dotsConfig: {
+ visible: false
+ }
+};
+
+export const LIBCONFIG_502: ModalLibConfig = {
+ buttonsConfig: {
+ visible: false,
+ strategy: ButtonsStrategy.DEFAULT
+ },
+ slideConfig: {
+ infinite: false,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ },
+ previewConfig: {
+ visible: false
+ },
+ dotsConfig: {
+ visible: false
+ }
+};
+
+export const LIBCONFIG_503: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ size: {
+ width: '90px',
+ height: 'auto'
+ }
+ }
+};
+
+export const LIBCONFIG_504: ModalLibConfig = {
+ enableCloseOutside: false
+};
+
+export const LIBCONFIG_505: ModalLibConfig = {
+ currentImageConfig: { downloadable: false },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.DEFAULT
+ }
+};
+
+export const LIBCONFIG_506: ModalLibConfig = {
+ currentImageConfig: { downloadable: true },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.DEFAULT
+ }
+};
+
+export const LIBCONFIG_507: ModalLibConfig = {
+ currentImageConfig: { downloadable: true },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.SIMPLE
+ }
+};
+
+export const LIBCONFIG_508: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_509: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: true,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_510: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: false,
+ type: LoadingType.STANDARD
+ }
+ }
+};
+export const LIBCONFIG_511: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.STANDARD
+ }
+ }
+};
+export const LIBCONFIG_512: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.CIRCULAR
+ }
+ }
+};
+export const LIBCONFIG_513: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.BARS
+ }
+ }
+};
+export const LIBCONFIG_514: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.DOTS
+ }
+ }
+};
+export const LIBCONFIG_515: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.CUBE_FLIPPING
+ }
+ }
+};
+export const LIBCONFIG_516: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.CIRCLES
+ }
+ }
+};
+export const LIBCONFIG_517: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.EXPLOSING_SQUARES
+ }
+ }
+};
+
+export const LIBCONFIG_518: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.DEFAULT
+ }
+};
+export const LIBCONFIG_519: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.SIMPLE
+ }
+};
+export const LIBCONFIG_520: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.ADVANCED
+ }
+};
+export const LIBCONFIG_521: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.FULL
+ }
+};
+
+// default buttons but extUrl will open the link in a new tab instead of the current one
+// this requires to specify all buttons manually (also if they are not really custom)
+export const LIBCONFIG_522: ModalLibConfig = {
+ currentImageConfig: { downloadable: true },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.CUSTOM,
+ buttons: [
+ {
+ className: 'ext-url-image',
+ type: ButtonType.EXTURL,
+ extUrlInNewTab: true // <--- this is the important thing to understand this example
+ },
+ {
+ className: 'download-image',
+ type: ButtonType.DOWNLOAD
+ },
+ {
+ className: 'close-image',
+ type: ButtonType.CLOSE
+ }
+ ]
+ }
+};
+
+export const LIBCONFIG_523: ModalLibConfig = {
+ currentImageConfig: {
+ downloadable: true
+ },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.CUSTOM,
+ buttons: [
+ // KS_DEFAULT_BTN_ROTATE,
+ KS_DEFAULT_BTN_FULL_SCREEN,
+ KS_DEFAULT_BTN_DELETE,
+ KS_DEFAULT_BTN_EXTURL,
+ KS_DEFAULT_BTN_DOWNLOAD,
+ KS_DEFAULT_BTN_CLOSE
+ ]
+ }
+};
+
+export const LIBCONFIG_524: ModalLibConfig = {
+ currentImageConfig: {
+ downloadable: true
+ },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.CUSTOM,
+ buttons: [
+ {
+ className: 'fas fa-plus white',
+ type: ButtonType.CUSTOM,
+ ariaLabel: 'custom plus aria label',
+ title: 'custom plus title',
+ fontSize: '20px'
+ },
+ {
+ className: 'fas fa-times white',
+ type: ButtonType.CLOSE,
+ ariaLabel: 'custom close aria label',
+ title: 'custom close title',
+ fontSize: '20px'
+ },
+ {
+ className: 'fas fa-download white',
+ type: ButtonType.DOWNLOAD,
+ ariaLabel: 'custom download aria label',
+ title: 'custom download title',
+ fontSize: '20px'
+ },
+ {
+ className: 'fas fa-external-link-alt white',
+ type: ButtonType.EXTURL,
+ ariaLabel: 'custom exturl aria label',
+ title: 'custom exturl title',
+ fontSize: '20px'
+ }
+ ]
+ }
+};
+
+export const LIBCONFIG_525: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ mobileVisible: true
+ }
+};
+
+// Examples C
+export const LIBCONFIG_600: ModalLibConfig = {
+ keyboardConfig: {
+ esc: 'KeyQ',
+ left: 'ArrowDown',
+ right: 'ArrowUp'
+ }
+};
+
+export const LIBCONFIG_601: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => '
+ }
+ }
+};
+
+export const LIBCONFIG_602: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.HIDE_IF_EMPTY,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => '
+ }
+ }
+};
+
+export const LIBCONFIG_603: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_HIDDEN,
+ // you should build this value programmatically with the result of (show)="..()" event
+ customFullDescription: 'Custom description of the current visible image'
+ // if customFullDescription !== undefined, all other fields will be ignored
+ // imageText: '',
+ // numberSeparator: '',
+ // beforeTextDescription: '',
+ }
+ }
+};
+
+export const LIBCONFIG_604: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ // you should build this value programmatically with the result of (show)="..()" event
+ customFullDescription: 'Custom description of the current visible image'
+ // if customFullDescription !== undefined, all other fields will be ignored
+ // imageText: '',
+ // numberSeparator: '',
+ // beforeTextDescription: '',
+ }
+ }
+};
+
+export const LIBCONFIG_605: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => '
+ }
+ }
+};
+
+export const LIBCONFIG_606: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => ',
+ style: {
+ bgColor: 'rgba(255,0,0,.5)',
+ textColor: 'blue',
+ marginTop: '3px',
+ marginBottom: '0px',
+ marginLeft: '5px',
+ marginRight: '5px',
+ position: 'absolute',
+ top: '0px',
+ height: '25px'
+ // be careful to use width, in particular with % values
+ }
+ }
+ }
+};
+
+export const LIBCONFIG_607: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ number: 1
+ }
+};
+
+export const LIBCONFIG_608: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ number: 5
+ }
+};
+
+export const LIBCONFIG_609: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ arrows: false
+ }
+};
+
+export const LIBCONFIG_610: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ clickable: false
+ }
+};
+
+export const LIBCONFIG_611: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ size: { width: '30px', height: '30px' }
+ }
+};
+
+export const LIBCONFIG_612: ModalLibConfig = {
+ accessibilityConfig: {
+ backgroundAriaLabel: 'CUSTOM Modal gallery full screen background',
+ backgroundTitle: 'CUSTOM background title',
+
+ plainGalleryContentAriaLabel: 'CUSTOM Plain gallery content',
+ plainGalleryContentTitle: 'CUSTOM plain gallery content title',
+
+ modalGalleryContentAriaLabel: 'CUSTOM Modal gallery content',
+ modalGalleryContentTitle: 'CUSTOM modal gallery content title',
+
+ loadingSpinnerAriaLabel: 'CUSTOM The current image is loading. Please be patient.',
+ loadingSpinnerTitle: 'CUSTOM The current image is loading. Please be patient.',
+
+ mainContainerAriaLabel: 'CUSTOM Current image and navigation',
+ mainContainerTitle: 'CUSTOM main container title',
+ mainPrevImageAriaLabel: 'CUSTOM Previous image',
+ mainPrevImageTitle: 'CUSTOM Previous image',
+ mainNextImageAriaLabel: 'CUSTOM Next image',
+ mainNextImageTitle: 'CUSTOM Next image',
+
+ dotsContainerAriaLabel: 'CUSTOM Image navigation dots',
+ dotsContainerTitle: 'CUSTOM dots container title',
+ dotAriaLabel: 'CUSTOM Navigate to image number',
+
+ previewsContainerAriaLabel: 'CUSTOM Image previews',
+ previewsContainerTitle: 'CUSTOM previews title',
+ previewScrollPrevAriaLabel: 'CUSTOM Scroll previous previews',
+ previewScrollPrevTitle: 'CUSTOM Scroll previous previews',
+ previewScrollNextAriaLabel: 'CUSTOM Scroll next previews',
+ previewScrollNextTitle: 'CUSTOM Scroll next previews',
+
+ carouselContainerAriaLabel: 'Current image and navigation',
+ carouselContainerTitle: '',
+ carouselPrevImageAriaLabel: 'Previous image',
+ carouselPrevImageTitle: 'Previous image',
+ carouselNextImageAriaLabel: 'Next image',
+ carouselNextImageTitle: 'Next image',
+ carouselPreviewsContainerAriaLabel: 'Image previews',
+ carouselPreviewsContainerTitle: '',
+ carouselPreviewScrollPrevAriaLabel: 'Scroll previous previews',
+ carouselPreviewScrollPrevTitle: 'Scroll previous previews',
+ carouselPreviewScrollNextAriaLabel: 'Scroll next previews',
+ carouselPreviewScrollNextTitle: 'Scroll next previews'
+ }
+};
+
+export const LIBCONFIG_613: ModalLibConfig = {
+ currentImageConfig: {
+ navigateOnClick: false
+ }
+};
+
+// Examples D
+export const LIBCONFIG_701: ModalLibConfig = {
+ currentImageConfig: {
+ downloadable: true
+ },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.SIMPLE
+ }
+};
+
+export const LIBCONFIG_702: ModalLibConfig = {
+ currentImageConfig: {
+ invertSwipe: true
+ }
+};
+
+export const LIBCONFIG_703: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: true,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+// Examples E
+export const LIBCONFIG_800: ModalLibConfig = {
+ slideConfig: {
+ playConfig: {
+ autoPlay: true,
+ interval: 5000,
+ pauseOnHover: true
+ }
+ }
+};
+
+export const LIBCONFIG_801: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ playConfig: {
+ autoPlay: true,
+ interval: 5000,
+ pauseOnHover: false
+ }
+ }
+};
+
+export const LIBCONFIG_802: ModalLibConfig = {
+ slideConfig: {
+ playConfig: {
+ autoPlay: true,
+ interval: 1000,
+ pauseOnHover: false
+ }
+ }
+};
+
+// Examples F
+export const LIBCONFIG_900: ModalLibConfig = {
+ slideConfig: {
+ infinite: false
+ },
+ currentImageConfig: {
+ loadingConfig: { enable: true, type: LoadingType.STANDARD } as LoadingConfig,
+ description: { strategy: DescriptionStrategy.ALWAYS_VISIBLE } as Description
+ }
+};
diff --git a/examples/angular-cli-material/src/app/modal-gallery/modal-gallery.component.ts b/examples/angular-cli-material/src/app/modal-gallery/modal-gallery.component.ts
new file mode 100644
index 00000000..716310a4
--- /dev/null
+++ b/examples/angular-cli-material/src/app/modal-gallery/modal-gallery.component.ts
@@ -0,0 +1,765 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { TemplateRef } from '@angular/core';
+import { ViewChild } from '@angular/core';
+import { Component, OnDestroy } from '@angular/core';
+import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
+
+import {
+ Action,
+ ButtonEvent,
+ ButtonType,
+ Image,
+ ImageModalEvent,
+ ModalGalleryService,
+ ModalGalleryRef,
+ ModalGalleryConfig,
+ ModalLibConfig
+} from '@ks89/angular-modal-gallery';
+import { Subscription } from 'rxjs';
+
+import * as libConfigs from './libconfigs';
+import { FormsModule } from '@angular/forms';
+import { NgTemplateOutlet } from '@angular/common';
+
+@Component({
+ selector: 'ks-modal-gallery-page',
+ templateUrl: './modal-gallery.html',
+ styleUrls: ['./modal-gallery.scss'],
+ imports: [FormsModule, NgTemplateOutlet]
+})
+export class ModalGalleryExampleComponent implements OnDestroy {
+ /**
+ * A custom template to illustrate the customization of previews rendering.
+ */
+ @ViewChild('previewsTemplate')
+ previewsTemplate?: TemplateRef;
+
+ imageIndex = 0;
+ galleryId = 1;
+ isPlaying = true;
+ // Examples A
+ CONFIG406: ModalLibConfig = libConfigs.LIBCONFIG_406;
+ CONFIG407: ModalLibConfig = libConfigs.LIBCONFIG_407;
+ CONFIG408: ModalLibConfig = libConfigs.LIBCONFIG_408;
+ // Examples B
+ CONFIG500: ModalLibConfig = libConfigs.LIBCONFIG_500;
+ CONFIG501: ModalLibConfig = libConfigs.LIBCONFIG_501;
+ CONFIG502: ModalLibConfig = libConfigs.LIBCONFIG_502;
+ CONFIG503: ModalLibConfig = libConfigs.LIBCONFIG_503;
+ CONFIG504: ModalLibConfig = libConfigs.LIBCONFIG_504;
+ CONFIG505: ModalLibConfig = libConfigs.LIBCONFIG_505;
+ CONFIG506: ModalLibConfig = libConfigs.LIBCONFIG_506;
+ CONFIG507: ModalLibConfig = libConfigs.LIBCONFIG_507;
+ CONFIG508: ModalLibConfig = libConfigs.LIBCONFIG_508;
+ CONFIG509: ModalLibConfig = libConfigs.LIBCONFIG_509;
+ CONFIG510: ModalLibConfig = libConfigs.LIBCONFIG_510;
+ CONFIG511: ModalLibConfig = libConfigs.LIBCONFIG_511;
+ CONFIG512: ModalLibConfig = libConfigs.LIBCONFIG_512;
+ CONFIG513: ModalLibConfig = libConfigs.LIBCONFIG_513;
+ CONFIG514: ModalLibConfig = libConfigs.LIBCONFIG_514;
+ CONFIG515: ModalLibConfig = libConfigs.LIBCONFIG_515;
+ CONFIG516: ModalLibConfig = libConfigs.LIBCONFIG_516;
+ CONFIG517: ModalLibConfig = libConfigs.LIBCONFIG_517;
+ CONFIG518: ModalLibConfig = libConfigs.LIBCONFIG_518;
+ CONFIG519: ModalLibConfig = libConfigs.LIBCONFIG_519;
+ CONFIG520: ModalLibConfig = libConfigs.LIBCONFIG_520;
+ CONFIG521: ModalLibConfig = libConfigs.LIBCONFIG_521;
+ CONFIG522: ModalLibConfig = libConfigs.LIBCONFIG_522;
+ CONFIG523: ModalLibConfig = libConfigs.LIBCONFIG_523;
+ CONFIG524: ModalLibConfig = libConfigs.LIBCONFIG_524;
+ CONFIG525: ModalLibConfig = libConfigs.LIBCONFIG_525;
+ // Examples C
+ CONFIG600: ModalLibConfig = libConfigs.LIBCONFIG_600;
+ CONFIG601: ModalLibConfig = libConfigs.LIBCONFIG_601;
+ CONFIG602: ModalLibConfig = libConfigs.LIBCONFIG_602;
+ CONFIG603: ModalLibConfig = libConfigs.LIBCONFIG_603;
+ CONFIG604: ModalLibConfig = libConfigs.LIBCONFIG_604;
+ CONFIG605: ModalLibConfig = libConfigs.LIBCONFIG_605;
+ CONFIG606: ModalLibConfig = libConfigs.LIBCONFIG_606;
+ CONFIG607: ModalLibConfig = libConfigs.LIBCONFIG_607;
+ CONFIG608: ModalLibConfig = libConfigs.LIBCONFIG_608;
+ CONFIG609: ModalLibConfig = libConfigs.LIBCONFIG_609;
+ CONFIG610: ModalLibConfig = libConfigs.LIBCONFIG_610;
+ CONFIG611: ModalLibConfig = libConfigs.LIBCONFIG_611;
+ CONFIG612: ModalLibConfig = libConfigs.LIBCONFIG_612;
+ CONFIG613: ModalLibConfig = libConfigs.LIBCONFIG_613;
+ // Examples D
+ CONFIG701: ModalLibConfig = libConfigs.LIBCONFIG_701;
+ CONFIG702: ModalLibConfig = libConfigs.LIBCONFIG_702;
+ CONFIG703: ModalLibConfig = libConfigs.LIBCONFIG_703;
+ // Examples E
+ CONFIG800: ModalLibConfig = libConfigs.LIBCONFIG_800;
+ CONFIG801: ModalLibConfig = libConfigs.LIBCONFIG_801;
+ CONFIG802: ModalLibConfig = libConfigs.LIBCONFIG_802;
+ // Example F
+ CONFIG900: ModalLibConfig = libConfigs.LIBCONFIG_900;
+
+ images: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img2.jpg',
+ description: 'Description 2'
+ }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/img3.jpg',
+ description: 'Description 3',
+ extUrl: 'http://www.google.com'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/img3.png',
+ title: 'custom title 2',
+ alt: 'custom alt 2',
+ ariaLabel: 'arial label 2'
+ }
+ ),
+ new Image(3, {
+ img: '../assets/images/gallery/img4.jpg',
+ description: 'Description 4',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(4, { img: '../assets/images/gallery/img5.jpg' }, { img: '../assets/images/gallery/thumbs/img5.jpg' })
+ ];
+
+ emptyImagesArray: Image[] = [];
+
+ imagesRectNoTitles: Image[] = [
+ new Image(
+ 0,
+ { img: '../assets/images/gallery/milan-pegasus-gallery-statue.jpg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg', title: '' }
+ ),
+ new Image(
+ 1,
+ { img: '../assets/images/gallery/pexels-photo-47223.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-47223.jpg', title: '' }
+ ),
+ new Image(
+ 2,
+ { img: '../assets/images/gallery/pexels-photo-52062.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-52062.jpg', title: '' }
+ ),
+ new Image(
+ 3,
+ { img: '../assets/images/gallery/pexels-photo-66943.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-66943.jpg', title: '' }
+ ),
+ new Image(
+ 4,
+ { img: '../assets/images/gallery/pexels-photo-93750.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-93750.jpg', title: '' }
+ ),
+ new Image(
+ 5,
+ { img: '../assets/images/gallery/pexels-photo-94420.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-94420.jpg', title: '' }
+ ),
+ new Image(
+ 6,
+ { img: '../assets/images/gallery/pexels-photo-96947.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-96947.jpg', title: '' }
+ )
+ ];
+
+ fallbackImages: Image[] = [
+ new Image(0, {
+ // this file is not available so the browser returns an error
+ img: '../assets/images/gallery/UNEXISTING_IMG1.jpg',
+ // because the img above doesn't exists, the library will use this file
+ fallbackImg: '../assets/images/gallery/fallback1.jpg'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/UNEXISTING_IMG2.jpg',
+ fallbackImg: '../assets/images/gallery/fallback2.jpg'
+ }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG3.jpg',
+ fallbackImg: '../assets/images/gallery/fallback3.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG3.png',
+ fallbackImg: '../assets/images/gallery/fallback3.jpg'
+ }
+ ),
+ new Image(3, {
+ img: '../assets/images/gallery/UNEXISTING_IMG4.jpg',
+ fallbackImg: '../assets/images/gallery/fallback4.jpg'
+ }),
+ new Image(
+ 4,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback5.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback5.jpg'
+ }
+ )
+ ];
+
+ // array of images (obviously with different id) where paths are the same.
+ // to prevent caching issues I have to append '?index'.
+ sameImages: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg?1',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img1.jpg?2',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(2, {
+ img: '../assets/images/gallery/img1.jpg?3',
+ extUrl: 'http://www.google.com'
+ })
+ ];
+
+ // example of a png converted into base64 using https://www.base64-image.de/ or other similar websites
+ base64String =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABN0lEQV' +
+ 'R4nO3SQQ2AQBDAwAVlaMEhCkAV' +
+ 'b2RcQmcU9NEZAAAAAOD/tvN675k5VoewxLOvLmAtA8QZIM4AcQaIM0CcAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4gwQZ4A4A8QZIM4AcQaIM0C' +
+ 'cAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4gwQZ4A4A8QZIM4AcQaIM0CcAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4g' +
+ 'wQZ4A4A8QZIM4AcQaIM0CcAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4gwQZ4A4A8QZIM4AcQaIM0CcAeIMEGeAOAPEGQAAAAAA4Pc+8asEoPPGq' +
+ 'xUAAAAASUVORK5CYII';
+
+ base64RedString =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAY1BMVEX/AAD/////WVn/+vr/qan/Nzf/ERH/2tr/s7P/KSn/' +
+ '7+//vr7/0ND/W1v/6+v/m5v/4+P/U1P/HR3/o6P/rq7/g4P/k5P/t7f/dXX/SEj/zMz/ZWX/h4f/bm7/amr/np7/yMhDG/2oAAAC8ElEQVR4nO3dC3KqQBCF4WkHERHFRyKIL/' +
+ 'a/ymDuVYMMFipTbbfnW8H5S4lQVGUMaWe4B3iHQvlQKB8K5UOhfCiUD4XyoVA+FJ7Myijd5dvBO9nmuzQqZ68X2mI9NO9suC7s84VxNuAO6GSQxU8VJvuQe3pn4T55uLDYcK9+' +
+ '0KZ4qDB574vPbej+HF2Fcc499km563p0FAbcQ18QdCi0B+6VLzk0fjtuC0dj7o0vGo/uF064B/agvFcYca/rRdReeOTe1pNjW6HkP6J1gbtQwzV4NnEVJtyrepU0C2M599ldhH' +
+ 'GjcMq9qWfT28KUe1Hv0nrhnHuPB/Na4YJ7jgeLv4UZ9xovsmuhXXKP8WJpL4Ur7i2erC6Fun4Kr8Jz4Rf3Em++/hdKf+htN/5XqOuGtC75LfzmnuHR96nQ6v2SVl9TWxVq/pKevq' +
+ 'aG1twjvFpXhTLeLz1rQMZyb/DMmhH3BM9GRudjxVVmtN51n62M1DdpXeVG2rveR22MxLe9jxgazfdsJ2Oj9en3THsfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
+ 'AAAAAAAAAAAAAAgHba/+98+AFnI+g/30L/GSX6z5nRf1aQ/vOe9J/Zpf/cNf1n533A+Yf6z7DUfw6p/rNkVX9Nkw850/kDzuXWf7Y6ab37Xl0K7ZJ7ixdLeykknQ8YGV0LacG9xo' +
+ 'MF/S2cc8/xYF4rpJR7T+9SqhfSlHtRz6Z0Wxjr+lEM40ahstvThJqFNOFe1aMJuQop4N7Vm4DchXTkXtaTI7UVUsS9rRcRtRequBZLuldII+mPw+MR3S8ke+De+JKDvQ1qFMr+kx' +
+ 'o0cxyFFEt945bHjhpXYXV/I/HN8DBxtrgLiQpp74Y3RUtJW2H1Oe7l3IuHe/fnd7+wuh4zGe+lBpnr+utSWLHF+r0vyeG6aPw+PFT4a1ZG6S7fDt7JNt+lUTnrsL5LoWwolA+F8q' +
+ 'FQPhTKh0L5UCgfCuVDoXw/lnQz7dm7GjoAAAAASUVORK5CYII=';
+ base64GreenString =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIAgMAAADQNkYNAAAADFBMVEUAAAAy/ysy/ysy/ysyTcibAAAAA3RSTlMA2r/af0d' +
+ 'WAAAAQUlEQVRo3u3YMREAMAzEsJAMyZJsMXy3XORdBFySJK3qxFXH1Y1DEARBEARBEARBEARBEARBkNmk436mvSRJ0o4eOKL2P81eyn8AAAAASUVORK5CYII=';
+
+ base64Image: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.base64String);
+ base64RedImage: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.base64RedString);
+ base64GreenImage: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.base64GreenString);
+
+ imagesBase64: Image[] = [
+ new Image(0, {
+ img: this.base64Image,
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: this.base64GreenImage,
+ description: 'Description 2'
+ }),
+ new Image(
+ 2,
+ {
+ img: this.base64RedImage,
+ description: 'Description 3',
+ extUrl: 'http://www.google.com'
+ },
+ {
+ img: this.base64RedImage,
+ title: 'custom title 2',
+ alt: 'custom alt 2',
+ ariaLabel: 'arial label 2'
+ }
+ )
+ ];
+
+ imagesCustomDownloadFileName: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg',
+ downloadFileName: 'first-img.jpg'
+ }),
+ new Image(1, {
+ img: this.base64Image,
+ downloadFileName: 'second-img-base64.jpg'
+ })
+ ];
+
+ imagesHtmlDescriptions: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img2.jpg',
+ description: '
C14 - (id=613) - Advanced demo - disable clicks on current image in modal-image
+
+
+
+
+
C15 - (id=614) - Advanced demo - after 3 seconds it closes modal gallery automatically with galleryService close method
+
Attention: please check the console to understand what it's happening!
+
Timer will clear and restart every time you show another image!
+
+
+
+
+
+
+
Other examples
+
+
D1 - (id=700) - Other demo - base64 images
+
+
+
+
+
D2 - (id=701) - Other demo - custom file name, used when downloaded
+
+
+
+
+
D3 - (id=702) - Other demo - invert touchscreen swipe direction
+
+
+
+
+
D4 - (id=703) - Other demo - infinite sliding and automatic add of images when the gallery is visible
+
+
+
+
+
D5 - (id=704) - Other demo - automatic update of the second image (index=1) via gallery service (open the second image and wait some seconds to see the result)
+
+
+
+
+
D6 - (id=705) - Other demo - remove title attribute on images
+
+
+
+
+
D7 - (id=706) - Other demo - modal gallery with an empty images array
+
+
+
+
+
+
+
AutoPlay examples
+
+
E1 - (id=800) - AutoPlay demo - with interval=5000 and pauseOnHover enabled
+
+
+
+
E2 - (id=801) - AutoPlay demo - with interval=5000, pauseOnHover disabled and infinite is enabled
+
+
+
+
E3 - (id=802) - AutoPlay demo - with interval=1000 and pauseOnHover disabled
+
+
+
+
+
+
+
+
+
Experimental examples*
+
* these will be either improved or removed in next versions
+
+
F1 - (id=900) - Experimental demo - infinite sliding with only one image to see if side arrows are hidden
+
+
+
+
F2 - (id=901) - Experimental demo - an array of Images with the same source file (different classes/ids and paths with appended '?imageIndex' to prevent caching issues)
P9 - (id=208) - row plain gallery layout with fallback images when it's not possible to load normal images (to fix issue #194)
+
+
+
+
+
+
P10 - (id=209) - row plain gallery layout with a tags and custom rectangular sizes (limit 4) + fallback images when it's not possible to load normal images (to fix issue #194)
+
+
+
+
+
+
P11 - (id=210) - row plain gallery layout (limit 2) and custom size + remove title attribute on images
+
+
+
+
+
+
P12 - (id=211) - row plain gallery layout with an empty images array
+
diff --git a/examples/universal/src/app/app.component.scss b/examples/universal/src/app/app.component.scss
new file mode 100644
index 00000000..2383946c
--- /dev/null
+++ b/examples/universal/src/app/app.component.scss
@@ -0,0 +1,47 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+
+#main-title {
+ text-align: center;
+ width: 100%;
+}
+
+#menu {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+
+ > .menu-item {
+ margin-left: 10px;
+ }
+}
+
+.copyright {
+ text-align: center;
+}
+
+.margin {
+ margin-left: 15px;
+ margin-right: 15px;
+}
diff --git a/examples/universal/src/app/app.component.ts b/examples/universal/src/app/app.component.ts
new file mode 100644
index 00000000..97621976
--- /dev/null
+++ b/examples/universal/src/app/app.component.ts
@@ -0,0 +1,37 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+import { RouterOutlet } from '@angular/router';
+
+import { NavbarComponent } from './navbar/navbar.component';
+
+@Component({
+ selector: 'ks-root',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.scss'],
+ imports: [NavbarComponent, RouterOutlet]
+})
+export class AppComponent {
+}
diff --git a/examples/universal/src/app/app.config.server.ts b/examples/universal/src/app/app.config.server.ts
new file mode 100644
index 00000000..41031f11
--- /dev/null
+++ b/examples/universal/src/app/app.config.server.ts
@@ -0,0 +1,12 @@
+import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
+import { provideServerRendering, withRoutes } from '@angular/ssr';
+import { appConfig } from './app.config';
+import { serverRoutes } from './app.routes.server';
+
+const serverConfig: ApplicationConfig = {
+ providers: [
+ provideServerRendering(withRoutes(serverRoutes))
+ ]
+};
+
+export const config = mergeApplicationConfig(appConfig, serverConfig);
diff --git a/examples/universal/src/app/app.config.ts b/examples/universal/src/app/app.config.ts
new file mode 100644
index 00000000..7db42f6b
--- /dev/null
+++ b/examples/universal/src/app/app.config.ts
@@ -0,0 +1,17 @@
+import { ApplicationConfig, importProvidersFrom, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
+import { provideRouter } from '@angular/router';
+import { BrowserModule, provideClientHydration, withEventReplay } from '@angular/platform-browser';
+import { FormsModule } from '@angular/forms';
+
+import { GalleryModule } from '@ks89/angular-modal-gallery';
+
+import { routes } from './app.routes';
+
+export const appConfig: ApplicationConfig = {
+ providers: [
+ provideBrowserGlobalErrorListeners(),
+ provideZoneChangeDetection({ eventCoalescing: true }),
+ provideRouter(routes), provideClientHydration(withEventReplay()),
+ importProvidersFrom(BrowserModule, FormsModule, GalleryModule)
+ ]
+};
diff --git a/examples/universal/src/app/app.routes.server.ts b/examples/universal/src/app/app.routes.server.ts
new file mode 100644
index 00000000..ffd37b1f
--- /dev/null
+++ b/examples/universal/src/app/app.routes.server.ts
@@ -0,0 +1,8 @@
+import { RenderMode, ServerRoute } from '@angular/ssr';
+
+export const serverRoutes: ServerRoute[] = [
+ {
+ path: '**',
+ renderMode: RenderMode.Prerender
+ }
+];
diff --git a/examples/universal/src/app/app.routes.ts b/examples/universal/src/app/app.routes.ts
new file mode 100644
index 00000000..f5ac5dce
--- /dev/null
+++ b/examples/universal/src/app/app.routes.ts
@@ -0,0 +1,13 @@
+import { Routes } from '@angular/router';
+
+import { HomeComponent } from './home/home.component';
+import { CarouselExampleComponent } from './carousel/carousel.component';
+import { ModalGalleryExampleComponent } from './modal-gallery/modal-gallery.component';
+import { PlainGalleryExampleComponent } from './plain-gallery/plain-gallery.component';
+
+export const routes: Routes = [
+ { path: '', component: HomeComponent },
+ { path: 'carousel', component: CarouselExampleComponent },
+ { path: 'modal', component: ModalGalleryExampleComponent },
+ { path: 'plain', component: PlainGalleryExampleComponent }
+];
diff --git a/examples/universal/src/app/carousel/carousel.component.ts b/examples/universal/src/app/carousel/carousel.component.ts
new file mode 100644
index 00000000..90cb52b0
--- /dev/null
+++ b/examples/universal/src/app/carousel/carousel.component.ts
@@ -0,0 +1,571 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+
+import {
+ AccessibilityConfig, CarouselLibConfig, Image, ImageEvent,
+ ModalGalleryConfig, ModalGalleryRef, ModalGalleryService, GalleryModule
+} from '@ks89/angular-modal-gallery';
+
+@Component({
+ selector: 'ks-carousel-page',
+ templateUrl: './carousel.html',
+ styleUrls: ['./carousel.scss'],
+ imports: [GalleryModule]
+})
+export class CarouselExampleComponent {
+ imageIndex = 1;
+ galleryId = 1;
+ autoPlay = true;
+ showArrows = true;
+ showDots = true;
+
+ LIBCONFIG102: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: false
+ }
+ };
+ LIBCONFIG103: CarouselLibConfig = {
+ carouselSlideInfinite: false
+ };
+ LIBCONFIG104: CarouselLibConfig = {
+ carouselDotsConfig: {
+ visible: false
+ }
+ };
+ LIBCONFIG105: CarouselLibConfig = {
+ carouselPlayConfig: {
+ autoPlay: false,
+ interval: 5000,
+ pauseOnHover: true
+ }
+ };
+ LIBCONFIG106: CarouselLibConfig = {
+ carouselPlayConfig: {
+ autoPlay: true,
+ interval: 10000,
+ pauseOnHover: false
+ }
+ };
+ LIBCONFIG107: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 7,
+ width: 'auto',
+ maxHeight: '100px'
+ }
+ };
+ LIBCONFIG113: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5
+ },
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ }
+ };
+ LIBCONFIG114: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '100px'
+ },
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: true
+ }
+ };
+ LIBCONFIG115: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '100px'
+ },
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: false,
+ modalGalleryEnable: false
+ }
+ };
+ LIBCONFIG116: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 7,
+ clickable: false
+ }
+ };
+ LIBCONFIG117: CarouselLibConfig = {
+ carouselImageConfig: {
+ invertSwipe: true
+ }
+ };
+ LIBCONFIG118: CarouselLibConfig = {
+ carouselImageConfig: {
+ description: {
+ strategy: 2
+ }
+ }
+ };
+ LIBCONFIG119: CarouselLibConfig = {
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ },
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '200px'
+ }
+ };
+ LIBCONFIG120: CarouselLibConfig = {
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ },
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '150px'
+ }
+ };
+ LIBCONFIG121: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ breakpoints: {
+ xSmall: 50,
+ small: 60,
+ medium: 80,
+ large: 150,
+ xLarge: 180
+ }
+ }
+ };
+ LIBCONFIG122: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ breakpoints: {
+ xSmall: 50,
+ small: 60,
+ medium: 70,
+ large: 80,
+ xLarge: 100
+ }
+ },
+ carouselConfig: {
+ maxWidth: '500px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ }
+ };
+ LIBCONFIG124: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ breakpoints: {
+ xSmall: 50,
+ small: 60,
+ medium: 70,
+ large: 80,
+ xLarge: 100
+ }
+ },
+ carouselConfig: {
+ maxWidth: '500px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ },
+ carouselPlayConfig: {
+ autoPlay: false,
+ interval: 1,
+ pauseOnHover: true
+ }
+ };
+
+ imagesRect: Image[] = [
+ new Image(
+ 0,
+ {
+ img: '/assets/images/gallery/milan-pegasus-gallery-statue.jpg',
+ description: 'Description 1'
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg',
+ title: 'First image title',
+ alt: 'First image alt',
+ ariaLabel: 'First image aria-label'
+ }
+ ),
+ new Image(1, { img: '/assets/images/gallery/pexels-photo-47223.jpeg' }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-47223.jpg' }),
+ new Image(
+ 2,
+ {
+ img: '/assets/images/gallery/pexels-photo-52062.jpeg',
+ description: 'Description 3',
+ title: 'Third image title',
+ alt: 'Third image alt',
+ ariaLabel: 'Third image aria-label'
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-52062.jpg',
+ description: 'Description 3'
+ }
+ ),
+ new Image(
+ 3,
+ {
+ img: '/assets/images/gallery/pexels-photo-66943.jpeg',
+ description: 'Description 4',
+ title: 'Fourth image title (modal obj)',
+ alt: 'Fourth image alt (modal obj)',
+ ariaLabel: 'Fourth image aria-label (modal obj)'
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-66943.jpg',
+ title: 'Fourth image title (plain obj)',
+ alt: 'Fourth image alt (plain obj)',
+ ariaLabel: 'Fourth image aria-label (plain obj)'
+ }
+ ),
+ new Image(4, { img: '/assets/images/gallery/pexels-photo-93750.jpeg' }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-93750.jpg' }),
+ new Image(
+ 5,
+ {
+ img: '/assets/images/gallery/pexels-photo-94420.jpeg',
+ description: 'Description 6'
+ },
+ { img: '/assets/images/gallery/thumbs/t-pexels-photo-94420.jpg' }
+ ),
+ new Image(6, { img: '/assets/images/gallery/pexels-photo-96947.jpeg' }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-96947.jpg' })
+ ];
+
+ imagesRectWithSources: Image[] = [
+ new Image(
+ 0,
+ {
+ img: '/assets/images/gallery/milan-pegasus-gallery-statue.jpg',
+ description: 'Description 1',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/milan-pegasus-gallery-statue-480w.jpg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/milan-pegasus-gallery-statue-768w.jpg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/milan-pegasus-gallery-statue-1024w.jpg' }
+ ]
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg',
+ title: 'First image title',
+ alt: 'First image alt',
+ ariaLabel: 'First image aria-label'
+ }
+ ),
+ new Image(1, {
+ img: '/assets/images/gallery/pexels-photo-47223.jpeg',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-47223-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-47223-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-47223-1024w.jpeg' }
+ ]
+ }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-47223.jpg' }),
+ new Image(
+ 2,
+ {
+ img: '/assets/images/gallery/pexels-photo-52062.jpeg',
+ description: 'Description 3',
+ title: 'Third image title',
+ alt: 'Third image alt',
+ ariaLabel: 'Third image aria-label',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-52062-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-52062-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-52062-1024w.jpeg' }
+ ]
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-52062.jpg',
+ description: 'Description 3'
+ }
+ ),
+ new Image(
+ 3,
+ {
+ img: '/assets/images/gallery/pexels-photo-66943.jpeg',
+ description: 'Description 4',
+ title: 'Fourth image title (modal obj)',
+ alt: 'Fourth image alt (modal obj)',
+ ariaLabel: 'Fourth image aria-label (modal obj)',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-66943-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-66943-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-66943-1024w.jpeg' }
+ ]
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-66943.jpg',
+ title: 'Fourth image title (plain obj)',
+ alt: 'Fourth image alt (plain obj)',
+ ariaLabel: 'Fourth image aria-label (plain obj)'
+ }
+ ),
+ new Image(4, {
+ img: '/assets/images/gallery/pexels-photo-93750.jpeg',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-93750-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-93750-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-93750-1024w.jpeg' }
+ ]
+ }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-93750.jpg' }),
+ new Image(
+ 5,
+ {
+ img: '/assets/images/gallery/pexels-photo-94420.jpeg',
+ description: 'Description 6',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-94420-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-94420-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-94420-1024w.jpeg' }
+ ]
+ },
+ { img: '/assets/images/gallery/thumbs/t-pexels-photo-94420.jpg' }
+ ),
+ new Image(6, {
+ img: '/assets/images/gallery/pexels-photo-96947.jpeg',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-96947-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-96947-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-96947-1024w.jpeg' }
+ ]
+ }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-96947.jpg' })
+ ];
+
+ emptyImagesArray: Image[] = [];
+
+ imagesRectNoTitles: Image[] = [
+ new Image(
+ 0,
+ { img: '../assets/images/gallery/milan-pegasus-gallery-statue.jpg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg', title: '' }
+ ),
+ new Image(
+ 1,
+ { img: '../assets/images/gallery/pexels-photo-47223.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-47223.jpg', title: '' }
+ ),
+ new Image(
+ 2,
+ { img: '../assets/images/gallery/pexels-photo-52062.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-52062.jpg', title: '' }
+ ),
+ new Image(
+ 3,
+ { img: '../assets/images/gallery/pexels-photo-66943.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-66943.jpg', title: '' }
+ ),
+ new Image(
+ 4,
+ { img: '../assets/images/gallery/pexels-photo-93750.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-93750.jpg', title: '' }
+ ),
+ new Image(
+ 5,
+ { img: '../assets/images/gallery/pexels-photo-94420.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-94420.jpg', title: '' }
+ ),
+ new Image(
+ 6,
+ { img: '../assets/images/gallery/pexels-photo-96947.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-96947.jpg', title: '' }
+ )
+ ];
+
+ fallbackRectImages: Image[] = [
+ new Image(0, {
+ // this file is not available so the browser returns an error
+ img: '../assets/images/gallery/UNEXISTING_IMG1.jpg',
+ // because the img above doesn't exists, the library will use this file
+ fallbackImg: '../assets/images/gallery/fallback-carousel1.jpg'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/UNEXISTING_IMG2.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel2.jpg'
+ }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG3.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel3.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG3.png',
+ fallbackImg: '../assets/images/gallery/fallback-carousel3.jpg'
+ }
+ ),
+ new Image(3, {
+ img: '../assets/images/gallery/UNEXISTING_IMG4.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel4.jpg'
+ }),
+ new Image(
+ 4,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel5.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel5.jpg'
+ }
+ )
+ ];
+
+ accessibilityConfig: AccessibilityConfig = {
+ backgroundAriaLabel: 'CUSTOM Modal gallery full screen background',
+ backgroundTitle: 'CUSTOM background title',
+
+ plainGalleryContentAriaLabel: 'CUSTOM Plain gallery content',
+ plainGalleryContentTitle: 'CUSTOM plain gallery content title',
+
+ modalGalleryContentAriaLabel: 'CUSTOM Modal gallery content',
+ modalGalleryContentTitle: 'CUSTOM modal gallery content title',
+
+ loadingSpinnerAriaLabel: 'CUSTOM The current image is loading. Please be patient.',
+ loadingSpinnerTitle: 'CUSTOM The current image is loading. Please be patient.',
+
+ mainContainerAriaLabel: 'CUSTOM Current image and navigation',
+ mainContainerTitle: 'CUSTOM main container title',
+ mainPrevImageAriaLabel: 'CUSTOM Previous image',
+ mainPrevImageTitle: 'CUSTOM Previous image',
+ mainNextImageAriaLabel: 'CUSTOM Next image',
+ mainNextImageTitle: 'CUSTOM Next image',
+
+ dotsContainerAriaLabel: 'CUSTOM Image navigation dots',
+ dotsContainerTitle: 'CUSTOM dots container title',
+ dotAriaLabel: 'CUSTOM Navigate to image number',
+
+ previewsContainerAriaLabel: 'CUSTOM Image previews',
+ previewsContainerTitle: 'CUSTOM previews title',
+ previewScrollPrevAriaLabel: 'CUSTOM Scroll previous previews',
+ previewScrollPrevTitle: 'CUSTOM Scroll previous previews',
+ previewScrollNextAriaLabel: 'CUSTOM Scroll next previews',
+ previewScrollNextTitle: 'CUSTOM Scroll next previews',
+
+ carouselContainerAriaLabel: 'Current image and navigation',
+ carouselContainerTitle: '',
+ carouselPrevImageAriaLabel: 'Previous image',
+ carouselPrevImageTitle: 'Previous image',
+ carouselNextImageAriaLabel: 'Next image',
+ carouselNextImageTitle: 'Next image',
+ carouselPreviewsContainerAriaLabel: 'Image previews',
+ carouselPreviewsContainerTitle: '',
+ carouselPreviewScrollPrevAriaLabel: 'Scroll previous previews',
+ carouselPreviewScrollPrevTitle: 'Scroll previous previews',
+ carouselPreviewScrollNextAriaLabel: 'Scroll next previews',
+ carouselPreviewScrollNextTitle: 'Scroll next previews'
+ };
+
+ constructor(private modalGalleryService: ModalGalleryService) {
+ }
+
+ onChangeAutoPlay(): void {
+ this.autoPlay = !this.autoPlay;
+ }
+
+ onChangeShowArrows(): void {
+ this.showArrows = !this.showArrows;
+ }
+
+ onChangeShowDots(): void {
+ this.showDots = !this.showDots;
+ }
+
+ // output evets
+ onShow(event: ImageEvent): void {
+ console.log('show', event);
+ }
+
+ onFirstImage(event: ImageEvent): void {
+ console.log('firstImage', event);
+ }
+
+ onLastImage(event: ImageEvent): void {
+ console.log('lastImage', event);
+ }
+
+ getLibConfig108(autoPlay: boolean, showArrows: boolean, showDots: boolean): CarouselLibConfig {
+ return {
+ carouselDotsConfig: {
+ visible: showDots
+ },
+ carouselPlayConfig: {
+ autoPlay: autoPlay,
+ interval: 3000,
+ pauseOnHover: true
+ },
+ carouselConfig: {
+ maxWidth: '100%',
+ maxHeight: '400px',
+ showArrows: showArrows,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ }
+ } as CarouselLibConfig;
+ }
+
+ openModal(imageIndex: number, id: number): void {
+ const imageToShow: Image = this.imagesRect[imageIndex];
+ const dialogRef: ModalGalleryRef = this.modalGalleryService.open({
+ id,
+ images: this.imagesRect,
+ currentImage: imageToShow
+ } as ModalGalleryConfig) as ModalGalleryRef;
+ }
+}
diff --git a/examples/universal/src/app/carousel/carousel.html b/examples/universal/src/app/carousel/carousel.html
new file mode 100644
index 00000000..5a618f01
--- /dev/null
+++ b/examples/universal/src/app/carousel/carousel.html
@@ -0,0 +1,216 @@
+
Carousel
+
+
+
Basic examples
+
+
+
A1 - (id=100) - carousel example (minimal with all defaults) without content projection
+
+
+
+
+
+
A2 - (id=101) - carousel example (minimal with all defaults)
+
+
+
This is my projected content!
+
+
+
+
+
A3 - (id=102) - carousel example without previews
+
+
+
This is my projected content!
+
+
+
+
+
A4 - (id=103) - carousel example without infinite sliding
+
+
+
This is my projected content!
+
+
+
+
+
A5 - (id=104) - carousel example without dots
+
+
+
This is my projected content!
+
+
+
+
+
A6 - (id=105) - carousel example without auto-play (but all other playConfig properties have default values)
+
+
+
This is my projected content!
+
+
+
+
+
A7 - (id=106) - carousel example with a custom playConfig (10s of interval and pauseOnHover disabled)
+
+
+
This is my projected content!
+
+
+
+
+
A8 - (id=107) - carousel example with a custom previewConfig (7 previews with 'auto' width and 100px of height)
+
+
+
This is my projected content!
+
+
+
+
+
A9 - (id=108) - carousel example with buttons to enable/disable autoplay, arrows and other properties
+
Autoplay:
+
Show Arrows:
+
Show Dots:
+
+
+
This is my projected content!
+
+
+
+
+
A10 - (id=109) - carousel example (minimal with all defaults) with outputs
+
+
+
+
+
+
A11 - (id=110) - carousel example with fallback images when it's not possible to load normal images (to fix issue #194)
+
+
+
+
+
+
A12 - (id=111) - carousel example without title attribute on images
+
+
+
+
+
+
A13 - (id=112) - carousel example with an empty images array.
+
Carousel component doesn't support empty images arrays! To prevent runtime errors, you should use an @if to remove the carousel when there are no images.
B1 - (id=113) - carousel example with fixed maxWidth (766px) and custom previews
+
By default, on bigger screen, previews will have height = 200px. If you want you can customize it or also change all breakpoint widths.
+
+
+
This is my projected content!
+
+
+
+
+
B2 - (id=114) - carousel example with fixed maxWidth (766px), custom previews and open modal on click
+
+
+
This is my projected content!
+
+
+
+
+
B3 - (id=115) - carousel example with fixed maxWidth (766px), custom previews and keyboard navigation disabled (for example left/right arrows)
+
+
+
This is my projected content!
+
+
+
+
+
B4 - (id=116) - carousel example with 7 images and unclickable previews
+
+
+
This is my projected content!
+
+
+
+
+
+
Examples with custom current image
+
+
C1 - (id=117) - carousel example with invert swipe on touchscreen devices
+
+
+
This is my projected content!
+
+
+
+
+
C2 - (id=118) - carousel example with description
+
+
+
This is my projected content!
+
+
+
+
+
+
Examples with custom previews height
+
I know that these examples look bad, but the purpose is to show only how the library handles heights.
+ In both examples I didn't set breakpoints, so it will be used default values, but with the maxHeight specified
+
If F1, the maxHeight is 200px, but also the default breakpoints has 200px as maximum size, so the result will be very bad on bigger screen.
+ In F2, I'm using a smaller maxHeight, so previews won't be taller than 150px, despite the default breakpoints on bigger screens (200px).
+
+
F1 - (id=119) - carousel example with fixed maxWidth (766px) and custom previews (maxHeight 200px)
+
+
+
This is my projected content!
+
+
+
+
+
F2 - (id=120) - carousel example with fixed maxWidth (766px) and custom previews (maxHeight 150px)
+
+
+
This is my projected content!
+
+
+
+
+
+
Examples with custom heights for previews (to try responsiveness)
+
+
+
G1 - (id=121) - carousel example (previews with different heights based on the window's width: xSmall: 50, small: 60, medium: 80, large: 150, xLarge: 180)
+
+
+
+
+
+
G2 - (id=122) - carousel example with fixed maxWidth (500px) (previews with different heights based on the window's width: xSmall: 50, small: 60, medium: 70, large: 80, xLarge: 100)
+
+
+
+
+
Examples using sources (to improve LCP)
+
+
+
H1 - (id=123) - carousel example (minimal with all defaults) without content projection - using sources
+
+
+
+
+
H2 - (id=124) - carousel example with fixed maxWidth (500px) (previews with different heights based on the window's width: xSmall: 50, small: 60, medium: 70, large: 80, xLarge: 100) - using sources
+
+
+
+
+
diff --git a/examples/universal/src/app/carousel/carousel.scss b/examples/universal/src/app/carousel/carousel.scss
new file mode 100644
index 00000000..622bda34
--- /dev/null
+++ b/examples/universal/src/app/carousel/carousel.scss
@@ -0,0 +1,157 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+:host {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+}
+
+section {
+ width: 100%;
+}
+
+h2, h3, p {
+ margin-left: 20px;
+}
+
+$text-color: #fff;
+$background: rgba(0, 0, 0, .7);
+
+.my-app-custom-plain-container-row {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+
+ .my-app-custom-image-row {
+ cursor: pointer;
+ height: auto;
+ margin-right: 2px;
+ width: 50px;
+
+ &.after {
+ border-top: 2px;
+ cursor: pointer;
+ display: none;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+ }
+}
+
+.my-app-custom-plain-container-column {
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+
+ .my-app-custom-image-column {
+ cursor: pointer;
+ height: auto;
+ margin-right: 2px;
+ width: 50px;
+
+ &.after {
+ border-top: 2px;
+ cursor: pointer;
+ display: none;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+ }
+}
+
+.my-app-custom-plain-container-with-desc {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+
+ figure {
+ margin: 0;
+ position: relative;
+
+ img {
+ max-width: 100%;
+ height: auto;
+ cursor: pointer;
+ }
+
+ figcaption {
+ background: rgba(0, 0, 0, .5);
+ color: #fff;
+ font-size: 85%;
+ padding: 5px;
+ position: absolute;
+ bottom: 3px;
+ left: 0;
+ right: 0;
+ }
+ }
+
+ .description {
+ font-weight: bold;
+ text-align: center;
+ }
+
+ .my-app-custom-image-with-desc {
+ height: auto;
+ margin-right: 2px;
+ width: 200px;
+ align-self: start;
+ }
+}
+
+.more {
+ background: $background;
+ cursor: pointer;
+ color: $text-color;
+ padding-top: 4px;
+ height: 46px;
+ position: absolute;
+ text-align: center;
+ width: 50px;
+}
+
+
+.projected {
+ color: white;
+ font-weight: 600;
+ font-size: 24px;
+ text-align: center;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ pointer-events: none;
+}
+
+.title {
+ margin-top: 40px;
+}
diff --git a/examples/universal/src/app/home/home.component.ts b/examples/universal/src/app/home/home.component.ts
new file mode 100644
index 00000000..9d08b6ac
--- /dev/null
+++ b/examples/universal/src/app/home/home.component.ts
@@ -0,0 +1,34 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+import { IntroHeaderComponent } from '../intro-header/intro-header.component';
+
+@Component({
+ selector: 'ks-home-page',
+ templateUrl: './home.html',
+ styleUrls: ['./home.scss'],
+ imports: [IntroHeaderComponent]
+})
+export class HomeComponent {}
diff --git a/examples/universal/src/app/home/home.html b/examples/universal/src/app/home/home.html
new file mode 100644
index 00000000..76dfa3b0
--- /dev/null
+++ b/examples/universal/src/app/home/home.html
@@ -0,0 +1,12 @@
+
+
+
+
+
Welcome
+
This is the official live example of @ks89/angular-modal-gallery.
+ To get started quickly you can try and check the sourcecode of this example.
+
+
diff --git a/examples/universal/src/app/home/home.scss b/examples/universal/src/app/home/home.scss
new file mode 100644
index 00000000..188db17b
--- /dev/null
+++ b/examples/universal/src/app/home/home.scss
@@ -0,0 +1,26 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+.container {
+ margin-left: 15px;
+ margin-right: 15px;
+}
diff --git a/examples/universal/src/app/intro-header/intro-header.component.ts b/examples/universal/src/app/intro-header/intro-header.component.ts
new file mode 100644
index 00000000..2f8069e1
--- /dev/null
+++ b/examples/universal/src/app/intro-header/intro-header.component.ts
@@ -0,0 +1,36 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+import { NgOptimizedImage } from '@angular/common';
+
+@Component({
+ selector: 'ks-intro-header',
+ templateUrl: 'intro-header.html',
+ imports: [
+ NgOptimizedImage
+ ],
+ styleUrls: ['intro-header.scss']
+})
+export class IntroHeaderComponent {}
diff --git a/examples/universal/src/app/intro-header/intro-header.html b/examples/universal/src/app/intro-header/intro-header.html
new file mode 100644
index 00000000..9209ac05
--- /dev/null
+++ b/examples/universal/src/app/intro-header/intro-header.html
@@ -0,0 +1,8 @@
+
+
+
@ks89/angular-modal-gallery
+
Image gallery for Angular >=20
+
Currently v14
+
+
diff --git a/examples/universal/src/app/intro-header/intro-header.scss b/examples/universal/src/app/intro-header/intro-header.scss
new file mode 100644
index 00000000..94e98c84
--- /dev/null
+++ b/examples/universal/src/app/intro-header/intro-header.scss
@@ -0,0 +1,73 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// search all occurrences in all code, also html, because it's everywhere
+$color-primary: #101010;
+$color-secondary: #9e9e9e;
+$color-white: #FFF;
+$color-black: #000;
+$color-light-black: #343A40;
+$color-code: #c100e0;
+$color-url: #0060b7;
+$color-warning: #880012;
+
+//$font-huge: 24px;
+$font-big: 18px;
+$font-middle: 14px;
+$font-small: 12px;
+$font-very-small: 10px;
+
+.intro-header {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: center;
+ background-color: $color-primary;
+ background: linear-gradient(135deg, $color-primary, $color-secondary);
+ margin-top: 10px;
+ padding-bottom: 1px;
+ color: $color-white;
+
+ h1 {
+ color: $color-white;
+ font-size: 3rem;
+ }
+}
+
+.project-title {
+ margin-top: 20px;
+ font-size: 2.8rem;
+ text-align: center;
+}
+
+img {
+ margin-top: 25px;
+ border-radius: 10px
+}
+
+.lead {
+ font-size: 1rem;
+ text-align: center;
+ color: #d4d4d4;
+}
diff --git a/examples/universal/src/app/modal-gallery/libconfigs.ts b/examples/universal/src/app/modal-gallery/libconfigs.ts
new file mode 100644
index 00000000..fdf8c729
--- /dev/null
+++ b/examples/universal/src/app/modal-gallery/libconfigs.ts
@@ -0,0 +1,583 @@
+import {
+ ButtonsStrategy,
+ ButtonType,
+ Description,
+ DescriptionStrategy,
+ KS_DEFAULT_BTN_CLOSE,
+ KS_DEFAULT_BTN_DELETE,
+ KS_DEFAULT_BTN_DOWNLOAD,
+ KS_DEFAULT_BTN_EXTURL,
+ KS_DEFAULT_BTN_FULL_SCREEN,
+ LoadingConfig,
+ LoadingType,
+ ModalLibConfig,
+ Size
+} from '@ks89/angular-modal-gallery';
+
+const DEFAULT_SIZE_PREVIEWS: Size = {
+ width: '100px',
+ height: 'auto'
+};
+
+// Examples A
+export const LIBCONFIG_406: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: true,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_407: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_408: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+// Examples B
+export const LIBCONFIG_500: ModalLibConfig = {
+ previewConfig: {
+ visible: false
+ },
+ dotsConfig: {
+ visible: false
+ }
+};
+
+export const LIBCONFIG_501: ModalLibConfig = {
+ buttonsConfig: {
+ visible: false,
+ strategy: ButtonsStrategy.DEFAULT
+ },
+ previewConfig: {
+ visible: false
+ },
+ dotsConfig: {
+ visible: false
+ }
+};
+
+export const LIBCONFIG_502: ModalLibConfig = {
+ buttonsConfig: {
+ visible: false,
+ strategy: ButtonsStrategy.DEFAULT
+ },
+ slideConfig: {
+ infinite: false,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ },
+ previewConfig: {
+ visible: false
+ },
+ dotsConfig: {
+ visible: false
+ }
+};
+
+export const LIBCONFIG_503: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ size: {
+ width: '90px',
+ height: 'auto'
+ }
+ }
+};
+
+export const LIBCONFIG_504: ModalLibConfig = {
+ enableCloseOutside: false
+};
+
+export const LIBCONFIG_505: ModalLibConfig = {
+ currentImageConfig: { downloadable: false },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.DEFAULT
+ }
+};
+
+export const LIBCONFIG_506: ModalLibConfig = {
+ currentImageConfig: { downloadable: true },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.DEFAULT
+ }
+};
+
+export const LIBCONFIG_507: ModalLibConfig = {
+ currentImageConfig: { downloadable: true },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.SIMPLE
+ }
+};
+
+export const LIBCONFIG_508: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_509: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: true,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_510: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: false,
+ type: LoadingType.STANDARD
+ }
+ }
+};
+export const LIBCONFIG_511: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.STANDARD
+ }
+ }
+};
+export const LIBCONFIG_512: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.CIRCULAR
+ }
+ }
+};
+export const LIBCONFIG_513: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.BARS
+ }
+ }
+};
+export const LIBCONFIG_514: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.DOTS
+ }
+ }
+};
+export const LIBCONFIG_515: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.CUBE_FLIPPING
+ }
+ }
+};
+export const LIBCONFIG_516: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.CIRCLES
+ }
+ }
+};
+export const LIBCONFIG_517: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.EXPLOSING_SQUARES
+ }
+ }
+};
+
+export const LIBCONFIG_518: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.DEFAULT
+ }
+};
+export const LIBCONFIG_519: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.SIMPLE
+ }
+};
+export const LIBCONFIG_520: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.ADVANCED
+ }
+};
+export const LIBCONFIG_521: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.FULL
+ }
+};
+
+// default buttons but extUrl will open the link in a new tab instead of the current one
+// this requires to specify all buttons manually (also if they are not really custom)
+export const LIBCONFIG_522: ModalLibConfig = {
+ currentImageConfig: { downloadable: true },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.CUSTOM,
+ buttons: [
+ {
+ className: 'ext-url-image',
+ type: ButtonType.EXTURL,
+ extUrlInNewTab: true // <--- this is the important thing to understand this example
+ },
+ {
+ className: 'download-image',
+ type: ButtonType.DOWNLOAD
+ },
+ {
+ className: 'close-image',
+ type: ButtonType.CLOSE
+ }
+ ]
+ }
+};
+
+export const LIBCONFIG_523: ModalLibConfig = {
+ currentImageConfig: {
+ downloadable: true
+ },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.CUSTOM,
+ buttons: [
+ // KS_DEFAULT_BTN_ROTATE,
+ KS_DEFAULT_BTN_FULL_SCREEN,
+ KS_DEFAULT_BTN_DELETE,
+ KS_DEFAULT_BTN_EXTURL,
+ KS_DEFAULT_BTN_DOWNLOAD,
+ KS_DEFAULT_BTN_CLOSE
+ ]
+ }
+};
+
+export const LIBCONFIG_524: ModalLibConfig = {
+ currentImageConfig: {
+ downloadable: true
+ },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.CUSTOM,
+ buttons: [
+ {
+ className: 'fas fa-plus white',
+ type: ButtonType.CUSTOM,
+ ariaLabel: 'custom plus aria label',
+ title: 'custom plus title',
+ fontSize: '20px'
+ },
+ {
+ className: 'fas fa-times white',
+ type: ButtonType.CLOSE,
+ ariaLabel: 'custom close aria label',
+ title: 'custom close title',
+ fontSize: '20px'
+ },
+ {
+ className: 'fas fa-download white',
+ type: ButtonType.DOWNLOAD,
+ ariaLabel: 'custom download aria label',
+ title: 'custom download title',
+ fontSize: '20px'
+ },
+ {
+ className: 'fas fa-external-link-alt white',
+ type: ButtonType.EXTURL,
+ ariaLabel: 'custom exturl aria label',
+ title: 'custom exturl title',
+ fontSize: '20px'
+ }
+ ]
+ }
+};
+
+export const LIBCONFIG_525: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ mobileVisible: true
+ }
+};
+
+// Examples C
+export const LIBCONFIG_600: ModalLibConfig = {
+ keyboardConfig: {
+ esc: 'KeyQ',
+ left: 'ArrowDown',
+ right: 'ArrowUp'
+ }
+};
+
+export const LIBCONFIG_601: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => '
+ }
+ }
+};
+
+export const LIBCONFIG_602: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.HIDE_IF_EMPTY,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => '
+ }
+ }
+};
+
+export const LIBCONFIG_603: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_HIDDEN,
+ // you should build this value programmatically with the result of (show)="..()" event
+ customFullDescription: 'Custom description of the current visible image'
+ // if customFullDescription !== undefined, all other fields will be ignored
+ // imageText: '',
+ // numberSeparator: '',
+ // beforeTextDescription: '',
+ }
+ }
+};
+
+export const LIBCONFIG_604: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ // you should build this value programmatically with the result of (show)="..()" event
+ customFullDescription: 'Custom description of the current visible image'
+ // if customFullDescription !== undefined, all other fields will be ignored
+ // imageText: '',
+ // numberSeparator: '',
+ // beforeTextDescription: '',
+ }
+ }
+};
+
+export const LIBCONFIG_605: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => '
+ }
+ }
+};
+
+export const LIBCONFIG_606: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => ',
+ style: {
+ bgColor: 'rgba(255,0,0,.5)',
+ textColor: 'blue',
+ marginTop: '3px',
+ marginBottom: '0px',
+ marginLeft: '5px',
+ marginRight: '5px',
+ position: 'absolute',
+ top: '0px',
+ height: '25px'
+ // be careful to use width, in particular with % values
+ }
+ }
+ }
+};
+
+export const LIBCONFIG_607: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ number: 1
+ }
+};
+
+export const LIBCONFIG_608: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ number: 5
+ }
+};
+
+export const LIBCONFIG_609: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ arrows: false
+ }
+};
+
+export const LIBCONFIG_610: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ clickable: false
+ }
+};
+
+export const LIBCONFIG_611: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ size: { width: '30px', height: '30px' }
+ }
+};
+
+export const LIBCONFIG_612: ModalLibConfig = {
+ accessibilityConfig: {
+ backgroundAriaLabel: 'CUSTOM Modal gallery full screen background',
+ backgroundTitle: 'CUSTOM background title',
+
+ plainGalleryContentAriaLabel: 'CUSTOM Plain gallery content',
+ plainGalleryContentTitle: 'CUSTOM plain gallery content title',
+
+ modalGalleryContentAriaLabel: 'CUSTOM Modal gallery content',
+ modalGalleryContentTitle: 'CUSTOM modal gallery content title',
+
+ loadingSpinnerAriaLabel: 'CUSTOM The current image is loading. Please be patient.',
+ loadingSpinnerTitle: 'CUSTOM The current image is loading. Please be patient.',
+
+ mainContainerAriaLabel: 'CUSTOM Current image and navigation',
+ mainContainerTitle: 'CUSTOM main container title',
+ mainPrevImageAriaLabel: 'CUSTOM Previous image',
+ mainPrevImageTitle: 'CUSTOM Previous image',
+ mainNextImageAriaLabel: 'CUSTOM Next image',
+ mainNextImageTitle: 'CUSTOM Next image',
+
+ dotsContainerAriaLabel: 'CUSTOM Image navigation dots',
+ dotsContainerTitle: 'CUSTOM dots container title',
+ dotAriaLabel: 'CUSTOM Navigate to image number',
+
+ previewsContainerAriaLabel: 'CUSTOM Image previews',
+ previewsContainerTitle: 'CUSTOM previews title',
+ previewScrollPrevAriaLabel: 'CUSTOM Scroll previous previews',
+ previewScrollPrevTitle: 'CUSTOM Scroll previous previews',
+ previewScrollNextAriaLabel: 'CUSTOM Scroll next previews',
+ previewScrollNextTitle: 'CUSTOM Scroll next previews',
+
+ carouselContainerAriaLabel: 'Current image and navigation',
+ carouselContainerTitle: '',
+ carouselPrevImageAriaLabel: 'Previous image',
+ carouselPrevImageTitle: 'Previous image',
+ carouselNextImageAriaLabel: 'Next image',
+ carouselNextImageTitle: 'Next image',
+ carouselPreviewsContainerAriaLabel: 'Image previews',
+ carouselPreviewsContainerTitle: '',
+ carouselPreviewScrollPrevAriaLabel: 'Scroll previous previews',
+ carouselPreviewScrollPrevTitle: 'Scroll previous previews',
+ carouselPreviewScrollNextAriaLabel: 'Scroll next previews',
+ carouselPreviewScrollNextTitle: 'Scroll next previews'
+ }
+};
+
+export const LIBCONFIG_613: ModalLibConfig = {
+ currentImageConfig: {
+ navigateOnClick: false
+ }
+};
+
+// Examples D
+export const LIBCONFIG_701: ModalLibConfig = {
+ currentImageConfig: {
+ downloadable: true
+ },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.SIMPLE
+ }
+};
+
+export const LIBCONFIG_702: ModalLibConfig = {
+ currentImageConfig: {
+ invertSwipe: true
+ }
+};
+
+export const LIBCONFIG_703: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: true,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+// Examples E
+export const LIBCONFIG_800: ModalLibConfig = {
+ slideConfig: {
+ playConfig: {
+ autoPlay: true,
+ interval: 5000,
+ pauseOnHover: true
+ }
+ }
+};
+
+export const LIBCONFIG_801: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ playConfig: {
+ autoPlay: true,
+ interval: 5000,
+ pauseOnHover: false
+ }
+ }
+};
+
+export const LIBCONFIG_802: ModalLibConfig = {
+ slideConfig: {
+ playConfig: {
+ autoPlay: true,
+ interval: 1000,
+ pauseOnHover: false
+ }
+ }
+};
+
+// Examples F
+export const LIBCONFIG_900: ModalLibConfig = {
+ slideConfig: {
+ infinite: false
+ },
+ currentImageConfig: {
+ loadingConfig: { enable: true, type: LoadingType.STANDARD } as LoadingConfig,
+ description: { strategy: DescriptionStrategy.ALWAYS_VISIBLE } as Description
+ }
+};
diff --git a/examples/universal/src/app/modal-gallery/modal-gallery.component.ts b/examples/universal/src/app/modal-gallery/modal-gallery.component.ts
new file mode 100644
index 00000000..716310a4
--- /dev/null
+++ b/examples/universal/src/app/modal-gallery/modal-gallery.component.ts
@@ -0,0 +1,765 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { TemplateRef } from '@angular/core';
+import { ViewChild } from '@angular/core';
+import { Component, OnDestroy } from '@angular/core';
+import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
+
+import {
+ Action,
+ ButtonEvent,
+ ButtonType,
+ Image,
+ ImageModalEvent,
+ ModalGalleryService,
+ ModalGalleryRef,
+ ModalGalleryConfig,
+ ModalLibConfig
+} from '@ks89/angular-modal-gallery';
+import { Subscription } from 'rxjs';
+
+import * as libConfigs from './libconfigs';
+import { FormsModule } from '@angular/forms';
+import { NgTemplateOutlet } from '@angular/common';
+
+@Component({
+ selector: 'ks-modal-gallery-page',
+ templateUrl: './modal-gallery.html',
+ styleUrls: ['./modal-gallery.scss'],
+ imports: [FormsModule, NgTemplateOutlet]
+})
+export class ModalGalleryExampleComponent implements OnDestroy {
+ /**
+ * A custom template to illustrate the customization of previews rendering.
+ */
+ @ViewChild('previewsTemplate')
+ previewsTemplate?: TemplateRef;
+
+ imageIndex = 0;
+ galleryId = 1;
+ isPlaying = true;
+ // Examples A
+ CONFIG406: ModalLibConfig = libConfigs.LIBCONFIG_406;
+ CONFIG407: ModalLibConfig = libConfigs.LIBCONFIG_407;
+ CONFIG408: ModalLibConfig = libConfigs.LIBCONFIG_408;
+ // Examples B
+ CONFIG500: ModalLibConfig = libConfigs.LIBCONFIG_500;
+ CONFIG501: ModalLibConfig = libConfigs.LIBCONFIG_501;
+ CONFIG502: ModalLibConfig = libConfigs.LIBCONFIG_502;
+ CONFIG503: ModalLibConfig = libConfigs.LIBCONFIG_503;
+ CONFIG504: ModalLibConfig = libConfigs.LIBCONFIG_504;
+ CONFIG505: ModalLibConfig = libConfigs.LIBCONFIG_505;
+ CONFIG506: ModalLibConfig = libConfigs.LIBCONFIG_506;
+ CONFIG507: ModalLibConfig = libConfigs.LIBCONFIG_507;
+ CONFIG508: ModalLibConfig = libConfigs.LIBCONFIG_508;
+ CONFIG509: ModalLibConfig = libConfigs.LIBCONFIG_509;
+ CONFIG510: ModalLibConfig = libConfigs.LIBCONFIG_510;
+ CONFIG511: ModalLibConfig = libConfigs.LIBCONFIG_511;
+ CONFIG512: ModalLibConfig = libConfigs.LIBCONFIG_512;
+ CONFIG513: ModalLibConfig = libConfigs.LIBCONFIG_513;
+ CONFIG514: ModalLibConfig = libConfigs.LIBCONFIG_514;
+ CONFIG515: ModalLibConfig = libConfigs.LIBCONFIG_515;
+ CONFIG516: ModalLibConfig = libConfigs.LIBCONFIG_516;
+ CONFIG517: ModalLibConfig = libConfigs.LIBCONFIG_517;
+ CONFIG518: ModalLibConfig = libConfigs.LIBCONFIG_518;
+ CONFIG519: ModalLibConfig = libConfigs.LIBCONFIG_519;
+ CONFIG520: ModalLibConfig = libConfigs.LIBCONFIG_520;
+ CONFIG521: ModalLibConfig = libConfigs.LIBCONFIG_521;
+ CONFIG522: ModalLibConfig = libConfigs.LIBCONFIG_522;
+ CONFIG523: ModalLibConfig = libConfigs.LIBCONFIG_523;
+ CONFIG524: ModalLibConfig = libConfigs.LIBCONFIG_524;
+ CONFIG525: ModalLibConfig = libConfigs.LIBCONFIG_525;
+ // Examples C
+ CONFIG600: ModalLibConfig = libConfigs.LIBCONFIG_600;
+ CONFIG601: ModalLibConfig = libConfigs.LIBCONFIG_601;
+ CONFIG602: ModalLibConfig = libConfigs.LIBCONFIG_602;
+ CONFIG603: ModalLibConfig = libConfigs.LIBCONFIG_603;
+ CONFIG604: ModalLibConfig = libConfigs.LIBCONFIG_604;
+ CONFIG605: ModalLibConfig = libConfigs.LIBCONFIG_605;
+ CONFIG606: ModalLibConfig = libConfigs.LIBCONFIG_606;
+ CONFIG607: ModalLibConfig = libConfigs.LIBCONFIG_607;
+ CONFIG608: ModalLibConfig = libConfigs.LIBCONFIG_608;
+ CONFIG609: ModalLibConfig = libConfigs.LIBCONFIG_609;
+ CONFIG610: ModalLibConfig = libConfigs.LIBCONFIG_610;
+ CONFIG611: ModalLibConfig = libConfigs.LIBCONFIG_611;
+ CONFIG612: ModalLibConfig = libConfigs.LIBCONFIG_612;
+ CONFIG613: ModalLibConfig = libConfigs.LIBCONFIG_613;
+ // Examples D
+ CONFIG701: ModalLibConfig = libConfigs.LIBCONFIG_701;
+ CONFIG702: ModalLibConfig = libConfigs.LIBCONFIG_702;
+ CONFIG703: ModalLibConfig = libConfigs.LIBCONFIG_703;
+ // Examples E
+ CONFIG800: ModalLibConfig = libConfigs.LIBCONFIG_800;
+ CONFIG801: ModalLibConfig = libConfigs.LIBCONFIG_801;
+ CONFIG802: ModalLibConfig = libConfigs.LIBCONFIG_802;
+ // Example F
+ CONFIG900: ModalLibConfig = libConfigs.LIBCONFIG_900;
+
+ images: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img2.jpg',
+ description: 'Description 2'
+ }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/img3.jpg',
+ description: 'Description 3',
+ extUrl: 'http://www.google.com'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/img3.png',
+ title: 'custom title 2',
+ alt: 'custom alt 2',
+ ariaLabel: 'arial label 2'
+ }
+ ),
+ new Image(3, {
+ img: '../assets/images/gallery/img4.jpg',
+ description: 'Description 4',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(4, { img: '../assets/images/gallery/img5.jpg' }, { img: '../assets/images/gallery/thumbs/img5.jpg' })
+ ];
+
+ emptyImagesArray: Image[] = [];
+
+ imagesRectNoTitles: Image[] = [
+ new Image(
+ 0,
+ { img: '../assets/images/gallery/milan-pegasus-gallery-statue.jpg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg', title: '' }
+ ),
+ new Image(
+ 1,
+ { img: '../assets/images/gallery/pexels-photo-47223.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-47223.jpg', title: '' }
+ ),
+ new Image(
+ 2,
+ { img: '../assets/images/gallery/pexels-photo-52062.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-52062.jpg', title: '' }
+ ),
+ new Image(
+ 3,
+ { img: '../assets/images/gallery/pexels-photo-66943.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-66943.jpg', title: '' }
+ ),
+ new Image(
+ 4,
+ { img: '../assets/images/gallery/pexels-photo-93750.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-93750.jpg', title: '' }
+ ),
+ new Image(
+ 5,
+ { img: '../assets/images/gallery/pexels-photo-94420.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-94420.jpg', title: '' }
+ ),
+ new Image(
+ 6,
+ { img: '../assets/images/gallery/pexels-photo-96947.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-96947.jpg', title: '' }
+ )
+ ];
+
+ fallbackImages: Image[] = [
+ new Image(0, {
+ // this file is not available so the browser returns an error
+ img: '../assets/images/gallery/UNEXISTING_IMG1.jpg',
+ // because the img above doesn't exists, the library will use this file
+ fallbackImg: '../assets/images/gallery/fallback1.jpg'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/UNEXISTING_IMG2.jpg',
+ fallbackImg: '../assets/images/gallery/fallback2.jpg'
+ }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG3.jpg',
+ fallbackImg: '../assets/images/gallery/fallback3.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG3.png',
+ fallbackImg: '../assets/images/gallery/fallback3.jpg'
+ }
+ ),
+ new Image(3, {
+ img: '../assets/images/gallery/UNEXISTING_IMG4.jpg',
+ fallbackImg: '../assets/images/gallery/fallback4.jpg'
+ }),
+ new Image(
+ 4,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback5.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback5.jpg'
+ }
+ )
+ ];
+
+ // array of images (obviously with different id) where paths are the same.
+ // to prevent caching issues I have to append '?index'.
+ sameImages: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg?1',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img1.jpg?2',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(2, {
+ img: '../assets/images/gallery/img1.jpg?3',
+ extUrl: 'http://www.google.com'
+ })
+ ];
+
+ // example of a png converted into base64 using https://www.base64-image.de/ or other similar websites
+ base64String =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABN0lEQV' +
+ 'R4nO3SQQ2AQBDAwAVlaMEhCkAV' +
+ 'b2RcQmcU9NEZAAAAAOD/tvN675k5VoewxLOvLmAtA8QZIM4AcQaIM0CcAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4gwQZ4A4A8QZIM4AcQaIM0C' +
+ 'cAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4gwQZ4A4A8QZIM4AcQaIM0CcAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4g' +
+ 'wQZ4A4A8QZIM4AcQaIM0CcAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4gwQZ4A4A8QZIM4AcQaIM0CcAeIMEGeAOAPEGQAAAAAA4Pc+8asEoPPGq' +
+ 'xUAAAAASUVORK5CYII';
+
+ base64RedString =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAY1BMVEX/AAD/////WVn/+vr/qan/Nzf/ERH/2tr/s7P/KSn/' +
+ '7+//vr7/0ND/W1v/6+v/m5v/4+P/U1P/HR3/o6P/rq7/g4P/k5P/t7f/dXX/SEj/zMz/ZWX/h4f/bm7/amr/np7/yMhDG/2oAAAC8ElEQVR4nO3dC3KqQBCF4WkHERHFRyKIL/' +
+ 'a/ymDuVYMMFipTbbfnW8H5S4lQVGUMaWe4B3iHQvlQKB8K5UOhfCiUD4XyoVA+FJ7Myijd5dvBO9nmuzQqZ68X2mI9NO9suC7s84VxNuAO6GSQxU8VJvuQe3pn4T55uLDYcK9+' +
+ '0KZ4qDB574vPbej+HF2Fcc499km563p0FAbcQ18QdCi0B+6VLzk0fjtuC0dj7o0vGo/uF064B/agvFcYca/rRdReeOTe1pNjW6HkP6J1gbtQwzV4NnEVJtyrepU0C2M599ldhH' +
+ 'GjcMq9qWfT28KUe1Hv0nrhnHuPB/Na4YJ7jgeLv4UZ9xovsmuhXXKP8WJpL4Ur7i2erC6Fun4Kr8Jz4Rf3Em++/hdKf+htN/5XqOuGtC75LfzmnuHR96nQ6v2SVl9TWxVq/pKevq' +
+ 'aG1twjvFpXhTLeLz1rQMZyb/DMmhH3BM9GRudjxVVmtN51n62M1DdpXeVG2rveR22MxLe9jxgazfdsJ2Oj9en3THsfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
+ 'AAAAAAAAAAAAAAgHba/+98+AFnI+g/30L/GSX6z5nRf1aQ/vOe9J/Zpf/cNf1n533A+Yf6z7DUfw6p/rNkVX9Nkw850/kDzuXWf7Y6ab37Xl0K7ZJ7ixdLeykknQ8YGV0LacG9xo' +
+ 'MF/S2cc8/xYF4rpJR7T+9SqhfSlHtRz6Z0Wxjr+lEM40ahstvThJqFNOFe1aMJuQop4N7Vm4DchXTkXtaTI7UVUsS9rRcRtRequBZLuldII+mPw+MR3S8ke+De+JKDvQ1qFMr+kx' +
+ 'o0cxyFFEt945bHjhpXYXV/I/HN8DBxtrgLiQpp74Y3RUtJW2H1Oe7l3IuHe/fnd7+wuh4zGe+lBpnr+utSWLHF+r0vyeG6aPw+PFT4a1ZG6S7fDt7JNt+lUTnrsL5LoWwolA+F8q' +
+ 'FQPhTKh0L5UCgfCuVDoXw/lnQz7dm7GjoAAAAASUVORK5CYII=';
+ base64GreenString =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIAgMAAADQNkYNAAAADFBMVEUAAAAy/ysy/ysy/ysyTcibAAAAA3RSTlMA2r/af0d' +
+ 'WAAAAQUlEQVRo3u3YMREAMAzEsJAMyZJsMXy3XORdBFySJK3qxFXH1Y1DEARBEARBEARBEARBEARBkNmk436mvSRJ0o4eOKL2P81eyn8AAAAASUVORK5CYII=';
+
+ base64Image: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.base64String);
+ base64RedImage: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.base64RedString);
+ base64GreenImage: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.base64GreenString);
+
+ imagesBase64: Image[] = [
+ new Image(0, {
+ img: this.base64Image,
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: this.base64GreenImage,
+ description: 'Description 2'
+ }),
+ new Image(
+ 2,
+ {
+ img: this.base64RedImage,
+ description: 'Description 3',
+ extUrl: 'http://www.google.com'
+ },
+ {
+ img: this.base64RedImage,
+ title: 'custom title 2',
+ alt: 'custom alt 2',
+ ariaLabel: 'arial label 2'
+ }
+ )
+ ];
+
+ imagesCustomDownloadFileName: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg',
+ downloadFileName: 'first-img.jpg'
+ }),
+ new Image(1, {
+ img: this.base64Image,
+ downloadFileName: 'second-img-base64.jpg'
+ })
+ ];
+
+ imagesHtmlDescriptions: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img2.jpg',
+ description: '
C14 - (id=613) - Advanced demo - disable clicks on current image in modal-image
+
+
+
+
+
C15 - (id=614) - Advanced demo - after 3 seconds it closes modal gallery automatically with galleryService close method
+
Attention: please check the console to understand what it's happening!
+
Timer will clear and restart every time you show another image!
+
+
+
+
+
+
+
Other examples
+
+
D1 - (id=700) - Other demo - base64 images
+
+
+
+
+
D2 - (id=701) - Other demo - custom file name, used when downloaded
+
+
+
+
+
D3 - (id=702) - Other demo - invert touchscreen swipe direction
+
+
+
+
+
D4 - (id=703) - Other demo - infinite sliding and automatic add of images when the gallery is visible
+
+
+
+
+
D5 - (id=704) - Other demo - automatic update of the second image (index=1) via gallery service (open the second image and wait some seconds to see the result)
+
+
+
+
+
D6 - (id=705) - Other demo - remove title attribute on images
+
+
+
+
+
D7 - (id=706) - Other demo - modal gallery with an empty images array
+
+
+
+
+
+
+
AutoPlay examples
+
+
E1 - (id=800) - AutoPlay demo - with interval=5000 and pauseOnHover enabled
+
+
+
+
E2 - (id=801) - AutoPlay demo - with interval=5000, pauseOnHover disabled and infinite is enabled
+
+
+
+
E3 - (id=802) - AutoPlay demo - with interval=1000 and pauseOnHover disabled
+
+
+
+
+
+
+
+
+
Experimental examples*
+
* these will be either improved or removed in next versions
+
+
F1 - (id=900) - Experimental demo - infinite sliding with only one image to see if side arrows are hidden
+
+
+
+
F2 - (id=901) - Experimental demo - an array of Images with the same source file (different classes/ids and paths with appended '?imageIndex' to prevent caching issues)
P9 - (id=208) - row plain gallery layout with fallback images when it's not possible to load normal images (to fix issue #194)
+
+
+
+
+
+
P10 - (id=209) - row plain gallery layout with a tags and custom rectangular sizes (limit 4) + fallback images when it's not possible to load normal images (to fix issue #194)
+
+
+
+
+
+
P11 - (id=210) - row plain gallery layout (limit 2) and custom size + remove title attribute on images
+
+
+
+
+
+
P12 - (id=211) - row plain gallery layout with an empty images array
+
diff --git a/src/app/app.component.scss b/src/app/app.component.scss
new file mode 100644
index 00000000..2383946c
--- /dev/null
+++ b/src/app/app.component.scss
@@ -0,0 +1,47 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+
+#main-title {
+ text-align: center;
+ width: 100%;
+}
+
+#menu {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+
+ > .menu-item {
+ margin-left: 10px;
+ }
+}
+
+.copyright {
+ text-align: center;
+}
+
+.margin {
+ margin-left: 15px;
+ margin-right: 15px;
+}
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
new file mode 100644
index 00000000..1279878b
--- /dev/null
+++ b/src/app/app.component.ts
@@ -0,0 +1,35 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+import { NavbarComponent } from './navbar/navbar.component';
+import { RouterOutlet } from '@angular/router';
+
+@Component({
+ selector: 'ks-root',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.scss'],
+ imports: [NavbarComponent, RouterOutlet]
+})
+export class AppComponent {}
diff --git a/src/app/app.config.ts b/src/app/app.config.ts
new file mode 100644
index 00000000..563e35ae
--- /dev/null
+++ b/src/app/app.config.ts
@@ -0,0 +1,23 @@
+import { ApplicationConfig, importProvidersFrom, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
+import { provideRouter } from '@angular/router';
+import { BrowserModule } from '@angular/platform-browser';
+import { FormsModule } from '@angular/forms';
+
+import { GalleryModule } from '@ks89/angular-modal-gallery';
+
+import { routes } from './app.routes';
+
+export const appConfig: ApplicationConfig = {
+ providers: [
+ provideBrowserGlobalErrorListeners(),
+ provideZoneChangeDetection({ eventCoalescing: true }),
+ provideRouter(routes),
+ importProvidersFrom(
+ BrowserModule,
+ FormsModule,
+
+ // import GalleryModule. Install @ks89/angular-modal-gallery first
+ GalleryModule
+ )
+ ]
+};
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
new file mode 100644
index 00000000..f91e522a
--- /dev/null
+++ b/src/app/app.routes.ts
@@ -0,0 +1,13 @@
+import { Routes } from '@angular/router';
+
+import { ModalGalleryExampleComponent } from './modal-gallery/modal-gallery.component';
+import { PlainGalleryExampleComponent } from './plain-gallery/plain-gallery.component';
+import { CarouselExampleComponent } from './carousel/carousel.component';
+import { HomeComponent } from './home/home.component';
+
+export const routes: Routes = [
+ { path: '', component: HomeComponent },
+ { path: 'carousel', component: CarouselExampleComponent },
+ { path: 'modal', component: ModalGalleryExampleComponent },
+ { path: 'plain', component: PlainGalleryExampleComponent }
+];
diff --git a/src/app/carousel/carousel.component.ts b/src/app/carousel/carousel.component.ts
new file mode 100644
index 00000000..90cb52b0
--- /dev/null
+++ b/src/app/carousel/carousel.component.ts
@@ -0,0 +1,571 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+
+import {
+ AccessibilityConfig, CarouselLibConfig, Image, ImageEvent,
+ ModalGalleryConfig, ModalGalleryRef, ModalGalleryService, GalleryModule
+} from '@ks89/angular-modal-gallery';
+
+@Component({
+ selector: 'ks-carousel-page',
+ templateUrl: './carousel.html',
+ styleUrls: ['./carousel.scss'],
+ imports: [GalleryModule]
+})
+export class CarouselExampleComponent {
+ imageIndex = 1;
+ galleryId = 1;
+ autoPlay = true;
+ showArrows = true;
+ showDots = true;
+
+ LIBCONFIG102: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: false
+ }
+ };
+ LIBCONFIG103: CarouselLibConfig = {
+ carouselSlideInfinite: false
+ };
+ LIBCONFIG104: CarouselLibConfig = {
+ carouselDotsConfig: {
+ visible: false
+ }
+ };
+ LIBCONFIG105: CarouselLibConfig = {
+ carouselPlayConfig: {
+ autoPlay: false,
+ interval: 5000,
+ pauseOnHover: true
+ }
+ };
+ LIBCONFIG106: CarouselLibConfig = {
+ carouselPlayConfig: {
+ autoPlay: true,
+ interval: 10000,
+ pauseOnHover: false
+ }
+ };
+ LIBCONFIG107: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 7,
+ width: 'auto',
+ maxHeight: '100px'
+ }
+ };
+ LIBCONFIG113: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5
+ },
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ }
+ };
+ LIBCONFIG114: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '100px'
+ },
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: true
+ }
+ };
+ LIBCONFIG115: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '100px'
+ },
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: false,
+ modalGalleryEnable: false
+ }
+ };
+ LIBCONFIG116: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 7,
+ clickable: false
+ }
+ };
+ LIBCONFIG117: CarouselLibConfig = {
+ carouselImageConfig: {
+ invertSwipe: true
+ }
+ };
+ LIBCONFIG118: CarouselLibConfig = {
+ carouselImageConfig: {
+ description: {
+ strategy: 2
+ }
+ }
+ };
+ LIBCONFIG119: CarouselLibConfig = {
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ },
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '200px'
+ }
+ };
+ LIBCONFIG120: CarouselLibConfig = {
+ carouselConfig: {
+ maxWidth: '766px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ },
+ carouselPreviewsConfig: {
+ visible: true,
+ number: 5,
+ width: 'auto',
+ maxHeight: '150px'
+ }
+ };
+ LIBCONFIG121: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ breakpoints: {
+ xSmall: 50,
+ small: 60,
+ medium: 80,
+ large: 150,
+ xLarge: 180
+ }
+ }
+ };
+ LIBCONFIG122: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ breakpoints: {
+ xSmall: 50,
+ small: 60,
+ medium: 70,
+ large: 80,
+ xLarge: 100
+ }
+ },
+ carouselConfig: {
+ maxWidth: '500px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ }
+ };
+ LIBCONFIG124: CarouselLibConfig = {
+ carouselPreviewsConfig: {
+ visible: true,
+ breakpoints: {
+ xSmall: 50,
+ small: 60,
+ medium: 70,
+ large: 80,
+ xLarge: 100
+ }
+ },
+ carouselConfig: {
+ maxWidth: '500px',
+ maxHeight: '400px',
+ showArrows: true,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ },
+ carouselPlayConfig: {
+ autoPlay: false,
+ interval: 1,
+ pauseOnHover: true
+ }
+ };
+
+ imagesRect: Image[] = [
+ new Image(
+ 0,
+ {
+ img: '/assets/images/gallery/milan-pegasus-gallery-statue.jpg',
+ description: 'Description 1'
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg',
+ title: 'First image title',
+ alt: 'First image alt',
+ ariaLabel: 'First image aria-label'
+ }
+ ),
+ new Image(1, { img: '/assets/images/gallery/pexels-photo-47223.jpeg' }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-47223.jpg' }),
+ new Image(
+ 2,
+ {
+ img: '/assets/images/gallery/pexels-photo-52062.jpeg',
+ description: 'Description 3',
+ title: 'Third image title',
+ alt: 'Third image alt',
+ ariaLabel: 'Third image aria-label'
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-52062.jpg',
+ description: 'Description 3'
+ }
+ ),
+ new Image(
+ 3,
+ {
+ img: '/assets/images/gallery/pexels-photo-66943.jpeg',
+ description: 'Description 4',
+ title: 'Fourth image title (modal obj)',
+ alt: 'Fourth image alt (modal obj)',
+ ariaLabel: 'Fourth image aria-label (modal obj)'
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-66943.jpg',
+ title: 'Fourth image title (plain obj)',
+ alt: 'Fourth image alt (plain obj)',
+ ariaLabel: 'Fourth image aria-label (plain obj)'
+ }
+ ),
+ new Image(4, { img: '/assets/images/gallery/pexels-photo-93750.jpeg' }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-93750.jpg' }),
+ new Image(
+ 5,
+ {
+ img: '/assets/images/gallery/pexels-photo-94420.jpeg',
+ description: 'Description 6'
+ },
+ { img: '/assets/images/gallery/thumbs/t-pexels-photo-94420.jpg' }
+ ),
+ new Image(6, { img: '/assets/images/gallery/pexels-photo-96947.jpeg' }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-96947.jpg' })
+ ];
+
+ imagesRectWithSources: Image[] = [
+ new Image(
+ 0,
+ {
+ img: '/assets/images/gallery/milan-pegasus-gallery-statue.jpg',
+ description: 'Description 1',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/milan-pegasus-gallery-statue-480w.jpg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/milan-pegasus-gallery-statue-768w.jpg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/milan-pegasus-gallery-statue-1024w.jpg' }
+ ]
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg',
+ title: 'First image title',
+ alt: 'First image alt',
+ ariaLabel: 'First image aria-label'
+ }
+ ),
+ new Image(1, {
+ img: '/assets/images/gallery/pexels-photo-47223.jpeg',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-47223-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-47223-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-47223-1024w.jpeg' }
+ ]
+ }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-47223.jpg' }),
+ new Image(
+ 2,
+ {
+ img: '/assets/images/gallery/pexels-photo-52062.jpeg',
+ description: 'Description 3',
+ title: 'Third image title',
+ alt: 'Third image alt',
+ ariaLabel: 'Third image aria-label',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-52062-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-52062-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-52062-1024w.jpeg' }
+ ]
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-52062.jpg',
+ description: 'Description 3'
+ }
+ ),
+ new Image(
+ 3,
+ {
+ img: '/assets/images/gallery/pexels-photo-66943.jpeg',
+ description: 'Description 4',
+ title: 'Fourth image title (modal obj)',
+ alt: 'Fourth image alt (modal obj)',
+ ariaLabel: 'Fourth image aria-label (modal obj)',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-66943-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-66943-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-66943-1024w.jpeg' }
+ ]
+ },
+ {
+ img: '/assets/images/gallery/thumbs/t-pexels-photo-66943.jpg',
+ title: 'Fourth image title (plain obj)',
+ alt: 'Fourth image alt (plain obj)',
+ ariaLabel: 'Fourth image aria-label (plain obj)'
+ }
+ ),
+ new Image(4, {
+ img: '/assets/images/gallery/pexels-photo-93750.jpeg',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-93750-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-93750-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-93750-1024w.jpeg' }
+ ]
+ }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-93750.jpg' }),
+ new Image(
+ 5,
+ {
+ img: '/assets/images/gallery/pexels-photo-94420.jpeg',
+ description: 'Description 6',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-94420-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-94420-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-94420-1024w.jpeg' }
+ ]
+ },
+ { img: '/assets/images/gallery/thumbs/t-pexels-photo-94420.jpg' }
+ ),
+ new Image(6, {
+ img: '/assets/images/gallery/pexels-photo-96947.jpeg',
+ sources: [
+ { media: '(max-width: 480px)', srcset: '/assets/images/gallery/pexels-photo-96947-480w.jpeg' },
+ { media: '(max-width: 768px)', srcset: '/assets/images/gallery/pexels-photo-96947-768w.jpeg' },
+ { media: '(max-width: 1024px)', srcset: '/assets/images/gallery/pexels-photo-96947-1024w.jpeg' }
+ ]
+ }, { img: '/assets/images/gallery/thumbs/t-pexels-photo-96947.jpg' })
+ ];
+
+ emptyImagesArray: Image[] = [];
+
+ imagesRectNoTitles: Image[] = [
+ new Image(
+ 0,
+ { img: '../assets/images/gallery/milan-pegasus-gallery-statue.jpg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg', title: '' }
+ ),
+ new Image(
+ 1,
+ { img: '../assets/images/gallery/pexels-photo-47223.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-47223.jpg', title: '' }
+ ),
+ new Image(
+ 2,
+ { img: '../assets/images/gallery/pexels-photo-52062.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-52062.jpg', title: '' }
+ ),
+ new Image(
+ 3,
+ { img: '../assets/images/gallery/pexels-photo-66943.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-66943.jpg', title: '' }
+ ),
+ new Image(
+ 4,
+ { img: '../assets/images/gallery/pexels-photo-93750.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-93750.jpg', title: '' }
+ ),
+ new Image(
+ 5,
+ { img: '../assets/images/gallery/pexels-photo-94420.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-94420.jpg', title: '' }
+ ),
+ new Image(
+ 6,
+ { img: '../assets/images/gallery/pexels-photo-96947.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-96947.jpg', title: '' }
+ )
+ ];
+
+ fallbackRectImages: Image[] = [
+ new Image(0, {
+ // this file is not available so the browser returns an error
+ img: '../assets/images/gallery/UNEXISTING_IMG1.jpg',
+ // because the img above doesn't exists, the library will use this file
+ fallbackImg: '../assets/images/gallery/fallback-carousel1.jpg'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/UNEXISTING_IMG2.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel2.jpg'
+ }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG3.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel3.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG3.png',
+ fallbackImg: '../assets/images/gallery/fallback-carousel3.jpg'
+ }
+ ),
+ new Image(3, {
+ img: '../assets/images/gallery/UNEXISTING_IMG4.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel4.jpg'
+ }),
+ new Image(
+ 4,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel5.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback-carousel5.jpg'
+ }
+ )
+ ];
+
+ accessibilityConfig: AccessibilityConfig = {
+ backgroundAriaLabel: 'CUSTOM Modal gallery full screen background',
+ backgroundTitle: 'CUSTOM background title',
+
+ plainGalleryContentAriaLabel: 'CUSTOM Plain gallery content',
+ plainGalleryContentTitle: 'CUSTOM plain gallery content title',
+
+ modalGalleryContentAriaLabel: 'CUSTOM Modal gallery content',
+ modalGalleryContentTitle: 'CUSTOM modal gallery content title',
+
+ loadingSpinnerAriaLabel: 'CUSTOM The current image is loading. Please be patient.',
+ loadingSpinnerTitle: 'CUSTOM The current image is loading. Please be patient.',
+
+ mainContainerAriaLabel: 'CUSTOM Current image and navigation',
+ mainContainerTitle: 'CUSTOM main container title',
+ mainPrevImageAriaLabel: 'CUSTOM Previous image',
+ mainPrevImageTitle: 'CUSTOM Previous image',
+ mainNextImageAriaLabel: 'CUSTOM Next image',
+ mainNextImageTitle: 'CUSTOM Next image',
+
+ dotsContainerAriaLabel: 'CUSTOM Image navigation dots',
+ dotsContainerTitle: 'CUSTOM dots container title',
+ dotAriaLabel: 'CUSTOM Navigate to image number',
+
+ previewsContainerAriaLabel: 'CUSTOM Image previews',
+ previewsContainerTitle: 'CUSTOM previews title',
+ previewScrollPrevAriaLabel: 'CUSTOM Scroll previous previews',
+ previewScrollPrevTitle: 'CUSTOM Scroll previous previews',
+ previewScrollNextAriaLabel: 'CUSTOM Scroll next previews',
+ previewScrollNextTitle: 'CUSTOM Scroll next previews',
+
+ carouselContainerAriaLabel: 'Current image and navigation',
+ carouselContainerTitle: '',
+ carouselPrevImageAriaLabel: 'Previous image',
+ carouselPrevImageTitle: 'Previous image',
+ carouselNextImageAriaLabel: 'Next image',
+ carouselNextImageTitle: 'Next image',
+ carouselPreviewsContainerAriaLabel: 'Image previews',
+ carouselPreviewsContainerTitle: '',
+ carouselPreviewScrollPrevAriaLabel: 'Scroll previous previews',
+ carouselPreviewScrollPrevTitle: 'Scroll previous previews',
+ carouselPreviewScrollNextAriaLabel: 'Scroll next previews',
+ carouselPreviewScrollNextTitle: 'Scroll next previews'
+ };
+
+ constructor(private modalGalleryService: ModalGalleryService) {
+ }
+
+ onChangeAutoPlay(): void {
+ this.autoPlay = !this.autoPlay;
+ }
+
+ onChangeShowArrows(): void {
+ this.showArrows = !this.showArrows;
+ }
+
+ onChangeShowDots(): void {
+ this.showDots = !this.showDots;
+ }
+
+ // output evets
+ onShow(event: ImageEvent): void {
+ console.log('show', event);
+ }
+
+ onFirstImage(event: ImageEvent): void {
+ console.log('firstImage', event);
+ }
+
+ onLastImage(event: ImageEvent): void {
+ console.log('lastImage', event);
+ }
+
+ getLibConfig108(autoPlay: boolean, showArrows: boolean, showDots: boolean): CarouselLibConfig {
+ return {
+ carouselDotsConfig: {
+ visible: showDots
+ },
+ carouselPlayConfig: {
+ autoPlay: autoPlay,
+ interval: 3000,
+ pauseOnHover: true
+ },
+ carouselConfig: {
+ maxWidth: '100%',
+ maxHeight: '400px',
+ showArrows: showArrows,
+ objectFit: 'cover',
+ keyboardEnable: true,
+ modalGalleryEnable: false
+ }
+ } as CarouselLibConfig;
+ }
+
+ openModal(imageIndex: number, id: number): void {
+ const imageToShow: Image = this.imagesRect[imageIndex];
+ const dialogRef: ModalGalleryRef = this.modalGalleryService.open({
+ id,
+ images: this.imagesRect,
+ currentImage: imageToShow
+ } as ModalGalleryConfig) as ModalGalleryRef;
+ }
+}
diff --git a/src/app/carousel/carousel.html b/src/app/carousel/carousel.html
new file mode 100644
index 00000000..5a618f01
--- /dev/null
+++ b/src/app/carousel/carousel.html
@@ -0,0 +1,216 @@
+
Carousel
+
+
+
Basic examples
+
+
+
A1 - (id=100) - carousel example (minimal with all defaults) without content projection
+
+
+
+
+
+
A2 - (id=101) - carousel example (minimal with all defaults)
+
+
+
This is my projected content!
+
+
+
+
+
A3 - (id=102) - carousel example without previews
+
+
+
This is my projected content!
+
+
+
+
+
A4 - (id=103) - carousel example without infinite sliding
+
+
+
This is my projected content!
+
+
+
+
+
A5 - (id=104) - carousel example without dots
+
+
+
This is my projected content!
+
+
+
+
+
A6 - (id=105) - carousel example without auto-play (but all other playConfig properties have default values)
+
+
+
This is my projected content!
+
+
+
+
+
A7 - (id=106) - carousel example with a custom playConfig (10s of interval and pauseOnHover disabled)
+
+
+
This is my projected content!
+
+
+
+
+
A8 - (id=107) - carousel example with a custom previewConfig (7 previews with 'auto' width and 100px of height)
+
+
+
This is my projected content!
+
+
+
+
+
A9 - (id=108) - carousel example with buttons to enable/disable autoplay, arrows and other properties
+
Autoplay:
+
Show Arrows:
+
Show Dots:
+
+
+
This is my projected content!
+
+
+
+
+
A10 - (id=109) - carousel example (minimal with all defaults) with outputs
+
+
+
+
+
+
A11 - (id=110) - carousel example with fallback images when it's not possible to load normal images (to fix issue #194)
+
+
+
+
+
+
A12 - (id=111) - carousel example without title attribute on images
+
+
+
+
+
+
A13 - (id=112) - carousel example with an empty images array.
+
Carousel component doesn't support empty images arrays! To prevent runtime errors, you should use an @if to remove the carousel when there are no images.
B1 - (id=113) - carousel example with fixed maxWidth (766px) and custom previews
+
By default, on bigger screen, previews will have height = 200px. If you want you can customize it or also change all breakpoint widths.
+
+
+
This is my projected content!
+
+
+
+
+
B2 - (id=114) - carousel example with fixed maxWidth (766px), custom previews and open modal on click
+
+
+
This is my projected content!
+
+
+
+
+
B3 - (id=115) - carousel example with fixed maxWidth (766px), custom previews and keyboard navigation disabled (for example left/right arrows)
+
+
+
This is my projected content!
+
+
+
+
+
B4 - (id=116) - carousel example with 7 images and unclickable previews
+
+
+
This is my projected content!
+
+
+
+
+
+
Examples with custom current image
+
+
C1 - (id=117) - carousel example with invert swipe on touchscreen devices
+
+
+
This is my projected content!
+
+
+
+
+
C2 - (id=118) - carousel example with description
+
+
+
This is my projected content!
+
+
+
+
+
+
Examples with custom previews height
+
I know that these examples look bad, but the purpose is to show only how the library handles heights.
+ In both examples I didn't set breakpoints, so it will be used default values, but with the maxHeight specified
+
If F1, the maxHeight is 200px, but also the default breakpoints has 200px as maximum size, so the result will be very bad on bigger screen.
+ In F2, I'm using a smaller maxHeight, so previews won't be taller than 150px, despite the default breakpoints on bigger screens (200px).
+
+
F1 - (id=119) - carousel example with fixed maxWidth (766px) and custom previews (maxHeight 200px)
+
+
+
This is my projected content!
+
+
+
+
+
F2 - (id=120) - carousel example with fixed maxWidth (766px) and custom previews (maxHeight 150px)
+
+
+
This is my projected content!
+
+
+
+
+
+
Examples with custom heights for previews (to try responsiveness)
+
+
+
G1 - (id=121) - carousel example (previews with different heights based on the window's width: xSmall: 50, small: 60, medium: 80, large: 150, xLarge: 180)
+
+
+
+
+
+
G2 - (id=122) - carousel example with fixed maxWidth (500px) (previews with different heights based on the window's width: xSmall: 50, small: 60, medium: 70, large: 80, xLarge: 100)
+
+
+
+
+
Examples using sources (to improve LCP)
+
+
+
H1 - (id=123) - carousel example (minimal with all defaults) without content projection - using sources
+
+
+
+
+
H2 - (id=124) - carousel example with fixed maxWidth (500px) (previews with different heights based on the window's width: xSmall: 50, small: 60, medium: 70, large: 80, xLarge: 100) - using sources
+
+
+
+
+
diff --git a/src/app/carousel/carousel.scss b/src/app/carousel/carousel.scss
new file mode 100644
index 00000000..622bda34
--- /dev/null
+++ b/src/app/carousel/carousel.scss
@@ -0,0 +1,157 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+:host {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+}
+
+section {
+ width: 100%;
+}
+
+h2, h3, p {
+ margin-left: 20px;
+}
+
+$text-color: #fff;
+$background: rgba(0, 0, 0, .7);
+
+.my-app-custom-plain-container-row {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+
+ .my-app-custom-image-row {
+ cursor: pointer;
+ height: auto;
+ margin-right: 2px;
+ width: 50px;
+
+ &.after {
+ border-top: 2px;
+ cursor: pointer;
+ display: none;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+ }
+}
+
+.my-app-custom-plain-container-column {
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+
+ .my-app-custom-image-column {
+ cursor: pointer;
+ height: auto;
+ margin-right: 2px;
+ width: 50px;
+
+ &.after {
+ border-top: 2px;
+ cursor: pointer;
+ display: none;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+ }
+}
+
+.my-app-custom-plain-container-with-desc {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+
+ figure {
+ margin: 0;
+ position: relative;
+
+ img {
+ max-width: 100%;
+ height: auto;
+ cursor: pointer;
+ }
+
+ figcaption {
+ background: rgba(0, 0, 0, .5);
+ color: #fff;
+ font-size: 85%;
+ padding: 5px;
+ position: absolute;
+ bottom: 3px;
+ left: 0;
+ right: 0;
+ }
+ }
+
+ .description {
+ font-weight: bold;
+ text-align: center;
+ }
+
+ .my-app-custom-image-with-desc {
+ height: auto;
+ margin-right: 2px;
+ width: 200px;
+ align-self: start;
+ }
+}
+
+.more {
+ background: $background;
+ cursor: pointer;
+ color: $text-color;
+ padding-top: 4px;
+ height: 46px;
+ position: absolute;
+ text-align: center;
+ width: 50px;
+}
+
+
+.projected {
+ color: white;
+ font-weight: 600;
+ font-size: 24px;
+ text-align: center;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ pointer-events: none;
+}
+
+.title {
+ margin-top: 40px;
+}
diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts
new file mode 100644
index 00000000..9d08b6ac
--- /dev/null
+++ b/src/app/home/home.component.ts
@@ -0,0 +1,34 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+import { IntroHeaderComponent } from '../intro-header/intro-header.component';
+
+@Component({
+ selector: 'ks-home-page',
+ templateUrl: './home.html',
+ styleUrls: ['./home.scss'],
+ imports: [IntroHeaderComponent]
+})
+export class HomeComponent {}
diff --git a/src/app/home/home.html b/src/app/home/home.html
new file mode 100644
index 00000000..76dfa3b0
--- /dev/null
+++ b/src/app/home/home.html
@@ -0,0 +1,12 @@
+
+
+
+
+
Welcome
+
This is the official live example of @ks89/angular-modal-gallery.
+ To get started quickly you can try and check the sourcecode of this example.
+
+
diff --git a/src/app/home/home.scss b/src/app/home/home.scss
new file mode 100644
index 00000000..188db17b
--- /dev/null
+++ b/src/app/home/home.scss
@@ -0,0 +1,26 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+.container {
+ margin-left: 15px;
+ margin-right: 15px;
+}
diff --git a/src/app/intro-header/intro-header.component.ts b/src/app/intro-header/intro-header.component.ts
new file mode 100644
index 00000000..2f8069e1
--- /dev/null
+++ b/src/app/intro-header/intro-header.component.ts
@@ -0,0 +1,36 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { Component } from '@angular/core';
+import { NgOptimizedImage } from '@angular/common';
+
+@Component({
+ selector: 'ks-intro-header',
+ templateUrl: 'intro-header.html',
+ imports: [
+ NgOptimizedImage
+ ],
+ styleUrls: ['intro-header.scss']
+})
+export class IntroHeaderComponent {}
diff --git a/src/app/intro-header/intro-header.html b/src/app/intro-header/intro-header.html
new file mode 100644
index 00000000..9209ac05
--- /dev/null
+++ b/src/app/intro-header/intro-header.html
@@ -0,0 +1,8 @@
+
+
+
@ks89/angular-modal-gallery
+
Image gallery for Angular >=20
+
Currently v14
+
+
diff --git a/src/app/intro-header/intro-header.scss b/src/app/intro-header/intro-header.scss
new file mode 100644
index 00000000..94e98c84
--- /dev/null
+++ b/src/app/intro-header/intro-header.scss
@@ -0,0 +1,73 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// search all occurrences in all code, also html, because it's everywhere
+$color-primary: #101010;
+$color-secondary: #9e9e9e;
+$color-white: #FFF;
+$color-black: #000;
+$color-light-black: #343A40;
+$color-code: #c100e0;
+$color-url: #0060b7;
+$color-warning: #880012;
+
+//$font-huge: 24px;
+$font-big: 18px;
+$font-middle: 14px;
+$font-small: 12px;
+$font-very-small: 10px;
+
+.intro-header {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: center;
+ background-color: $color-primary;
+ background: linear-gradient(135deg, $color-primary, $color-secondary);
+ margin-top: 10px;
+ padding-bottom: 1px;
+ color: $color-white;
+
+ h1 {
+ color: $color-white;
+ font-size: 3rem;
+ }
+}
+
+.project-title {
+ margin-top: 20px;
+ font-size: 2.8rem;
+ text-align: center;
+}
+
+img {
+ margin-top: 25px;
+ border-radius: 10px
+}
+
+.lead {
+ font-size: 1rem;
+ text-align: center;
+ color: #d4d4d4;
+}
diff --git a/src/app/modal-gallery/libconfigs.ts b/src/app/modal-gallery/libconfigs.ts
new file mode 100644
index 00000000..fdf8c729
--- /dev/null
+++ b/src/app/modal-gallery/libconfigs.ts
@@ -0,0 +1,583 @@
+import {
+ ButtonsStrategy,
+ ButtonType,
+ Description,
+ DescriptionStrategy,
+ KS_DEFAULT_BTN_CLOSE,
+ KS_DEFAULT_BTN_DELETE,
+ KS_DEFAULT_BTN_DOWNLOAD,
+ KS_DEFAULT_BTN_EXTURL,
+ KS_DEFAULT_BTN_FULL_SCREEN,
+ LoadingConfig,
+ LoadingType,
+ ModalLibConfig,
+ Size
+} from '@ks89/angular-modal-gallery';
+
+const DEFAULT_SIZE_PREVIEWS: Size = {
+ width: '100px',
+ height: 'auto'
+};
+
+// Examples A
+export const LIBCONFIG_406: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: true,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_407: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_408: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+// Examples B
+export const LIBCONFIG_500: ModalLibConfig = {
+ previewConfig: {
+ visible: false
+ },
+ dotsConfig: {
+ visible: false
+ }
+};
+
+export const LIBCONFIG_501: ModalLibConfig = {
+ buttonsConfig: {
+ visible: false,
+ strategy: ButtonsStrategy.DEFAULT
+ },
+ previewConfig: {
+ visible: false
+ },
+ dotsConfig: {
+ visible: false
+ }
+};
+
+export const LIBCONFIG_502: ModalLibConfig = {
+ buttonsConfig: {
+ visible: false,
+ strategy: ButtonsStrategy.DEFAULT
+ },
+ slideConfig: {
+ infinite: false,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ },
+ previewConfig: {
+ visible: false
+ },
+ dotsConfig: {
+ visible: false
+ }
+};
+
+export const LIBCONFIG_503: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ size: {
+ width: '90px',
+ height: 'auto'
+ }
+ }
+};
+
+export const LIBCONFIG_504: ModalLibConfig = {
+ enableCloseOutside: false
+};
+
+export const LIBCONFIG_505: ModalLibConfig = {
+ currentImageConfig: { downloadable: false },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.DEFAULT
+ }
+};
+
+export const LIBCONFIG_506: ModalLibConfig = {
+ currentImageConfig: { downloadable: true },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.DEFAULT
+ }
+};
+
+export const LIBCONFIG_507: ModalLibConfig = {
+ currentImageConfig: { downloadable: true },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.SIMPLE
+ }
+};
+
+export const LIBCONFIG_508: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: false,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_509: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: true,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+export const LIBCONFIG_510: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: false,
+ type: LoadingType.STANDARD
+ }
+ }
+};
+export const LIBCONFIG_511: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.STANDARD
+ }
+ }
+};
+export const LIBCONFIG_512: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.CIRCULAR
+ }
+ }
+};
+export const LIBCONFIG_513: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.BARS
+ }
+ }
+};
+export const LIBCONFIG_514: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.DOTS
+ }
+ }
+};
+export const LIBCONFIG_515: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.CUBE_FLIPPING
+ }
+ }
+};
+export const LIBCONFIG_516: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.CIRCLES
+ }
+ }
+};
+export const LIBCONFIG_517: ModalLibConfig = {
+ currentImageConfig: {
+ loadingConfig: {
+ enable: true,
+ type: LoadingType.EXPLOSING_SQUARES
+ }
+ }
+};
+
+export const LIBCONFIG_518: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.DEFAULT
+ }
+};
+export const LIBCONFIG_519: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.SIMPLE
+ }
+};
+export const LIBCONFIG_520: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.ADVANCED
+ }
+};
+export const LIBCONFIG_521: ModalLibConfig = {
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.FULL
+ }
+};
+
+// default buttons but extUrl will open the link in a new tab instead of the current one
+// this requires to specify all buttons manually (also if they are not really custom)
+export const LIBCONFIG_522: ModalLibConfig = {
+ currentImageConfig: { downloadable: true },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.CUSTOM,
+ buttons: [
+ {
+ className: 'ext-url-image',
+ type: ButtonType.EXTURL,
+ extUrlInNewTab: true // <--- this is the important thing to understand this example
+ },
+ {
+ className: 'download-image',
+ type: ButtonType.DOWNLOAD
+ },
+ {
+ className: 'close-image',
+ type: ButtonType.CLOSE
+ }
+ ]
+ }
+};
+
+export const LIBCONFIG_523: ModalLibConfig = {
+ currentImageConfig: {
+ downloadable: true
+ },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.CUSTOM,
+ buttons: [
+ // KS_DEFAULT_BTN_ROTATE,
+ KS_DEFAULT_BTN_FULL_SCREEN,
+ KS_DEFAULT_BTN_DELETE,
+ KS_DEFAULT_BTN_EXTURL,
+ KS_DEFAULT_BTN_DOWNLOAD,
+ KS_DEFAULT_BTN_CLOSE
+ ]
+ }
+};
+
+export const LIBCONFIG_524: ModalLibConfig = {
+ currentImageConfig: {
+ downloadable: true
+ },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.CUSTOM,
+ buttons: [
+ {
+ className: 'fas fa-plus white',
+ type: ButtonType.CUSTOM,
+ ariaLabel: 'custom plus aria label',
+ title: 'custom plus title',
+ fontSize: '20px'
+ },
+ {
+ className: 'fas fa-times white',
+ type: ButtonType.CLOSE,
+ ariaLabel: 'custom close aria label',
+ title: 'custom close title',
+ fontSize: '20px'
+ },
+ {
+ className: 'fas fa-download white',
+ type: ButtonType.DOWNLOAD,
+ ariaLabel: 'custom download aria label',
+ title: 'custom download title',
+ fontSize: '20px'
+ },
+ {
+ className: 'fas fa-external-link-alt white',
+ type: ButtonType.EXTURL,
+ ariaLabel: 'custom exturl aria label',
+ title: 'custom exturl title',
+ fontSize: '20px'
+ }
+ ]
+ }
+};
+
+export const LIBCONFIG_525: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ mobileVisible: true
+ }
+};
+
+// Examples C
+export const LIBCONFIG_600: ModalLibConfig = {
+ keyboardConfig: {
+ esc: 'KeyQ',
+ left: 'ArrowDown',
+ right: 'ArrowUp'
+ }
+};
+
+export const LIBCONFIG_601: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => '
+ }
+ }
+};
+
+export const LIBCONFIG_602: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.HIDE_IF_EMPTY,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => '
+ }
+ }
+};
+
+export const LIBCONFIG_603: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_HIDDEN,
+ // you should build this value programmatically with the result of (show)="..()" event
+ customFullDescription: 'Custom description of the current visible image'
+ // if customFullDescription !== undefined, all other fields will be ignored
+ // imageText: '',
+ // numberSeparator: '',
+ // beforeTextDescription: '',
+ }
+ }
+};
+
+export const LIBCONFIG_604: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ // you should build this value programmatically with the result of (show)="..()" event
+ customFullDescription: 'Custom description of the current visible image'
+ // if customFullDescription !== undefined, all other fields will be ignored
+ // imageText: '',
+ // numberSeparator: '',
+ // beforeTextDescription: '',
+ }
+ }
+};
+
+export const LIBCONFIG_605: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => '
+ }
+ }
+};
+
+export const LIBCONFIG_606: ModalLibConfig = {
+ currentImageConfig: {
+ description: {
+ strategy: DescriptionStrategy.ALWAYS_VISIBLE,
+ imageText: 'Look this image ',
+ numberSeparator: ' of ',
+ beforeTextDescription: ' => ',
+ style: {
+ bgColor: 'rgba(255,0,0,.5)',
+ textColor: 'blue',
+ marginTop: '3px',
+ marginBottom: '0px',
+ marginLeft: '5px',
+ marginRight: '5px',
+ position: 'absolute',
+ top: '0px',
+ height: '25px'
+ // be careful to use width, in particular with % values
+ }
+ }
+ }
+};
+
+export const LIBCONFIG_607: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ number: 1
+ }
+};
+
+export const LIBCONFIG_608: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ number: 5
+ }
+};
+
+export const LIBCONFIG_609: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ arrows: false
+ }
+};
+
+export const LIBCONFIG_610: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ clickable: false
+ }
+};
+
+export const LIBCONFIG_611: ModalLibConfig = {
+ previewConfig: {
+ visible: true,
+ size: { width: '30px', height: '30px' }
+ }
+};
+
+export const LIBCONFIG_612: ModalLibConfig = {
+ accessibilityConfig: {
+ backgroundAriaLabel: 'CUSTOM Modal gallery full screen background',
+ backgroundTitle: 'CUSTOM background title',
+
+ plainGalleryContentAriaLabel: 'CUSTOM Plain gallery content',
+ plainGalleryContentTitle: 'CUSTOM plain gallery content title',
+
+ modalGalleryContentAriaLabel: 'CUSTOM Modal gallery content',
+ modalGalleryContentTitle: 'CUSTOM modal gallery content title',
+
+ loadingSpinnerAriaLabel: 'CUSTOM The current image is loading. Please be patient.',
+ loadingSpinnerTitle: 'CUSTOM The current image is loading. Please be patient.',
+
+ mainContainerAriaLabel: 'CUSTOM Current image and navigation',
+ mainContainerTitle: 'CUSTOM main container title',
+ mainPrevImageAriaLabel: 'CUSTOM Previous image',
+ mainPrevImageTitle: 'CUSTOM Previous image',
+ mainNextImageAriaLabel: 'CUSTOM Next image',
+ mainNextImageTitle: 'CUSTOM Next image',
+
+ dotsContainerAriaLabel: 'CUSTOM Image navigation dots',
+ dotsContainerTitle: 'CUSTOM dots container title',
+ dotAriaLabel: 'CUSTOM Navigate to image number',
+
+ previewsContainerAriaLabel: 'CUSTOM Image previews',
+ previewsContainerTitle: 'CUSTOM previews title',
+ previewScrollPrevAriaLabel: 'CUSTOM Scroll previous previews',
+ previewScrollPrevTitle: 'CUSTOM Scroll previous previews',
+ previewScrollNextAriaLabel: 'CUSTOM Scroll next previews',
+ previewScrollNextTitle: 'CUSTOM Scroll next previews',
+
+ carouselContainerAriaLabel: 'Current image and navigation',
+ carouselContainerTitle: '',
+ carouselPrevImageAriaLabel: 'Previous image',
+ carouselPrevImageTitle: 'Previous image',
+ carouselNextImageAriaLabel: 'Next image',
+ carouselNextImageTitle: 'Next image',
+ carouselPreviewsContainerAriaLabel: 'Image previews',
+ carouselPreviewsContainerTitle: '',
+ carouselPreviewScrollPrevAriaLabel: 'Scroll previous previews',
+ carouselPreviewScrollPrevTitle: 'Scroll previous previews',
+ carouselPreviewScrollNextAriaLabel: 'Scroll next previews',
+ carouselPreviewScrollNextTitle: 'Scroll next previews'
+ }
+};
+
+export const LIBCONFIG_613: ModalLibConfig = {
+ currentImageConfig: {
+ navigateOnClick: false
+ }
+};
+
+// Examples D
+export const LIBCONFIG_701: ModalLibConfig = {
+ currentImageConfig: {
+ downloadable: true
+ },
+ buttonsConfig: {
+ visible: true,
+ strategy: ButtonsStrategy.SIMPLE
+ }
+};
+
+export const LIBCONFIG_702: ModalLibConfig = {
+ currentImageConfig: {
+ invertSwipe: true
+ }
+};
+
+export const LIBCONFIG_703: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ sidePreviews: {
+ show: true,
+ size: DEFAULT_SIZE_PREVIEWS
+ }
+ }
+};
+
+// Examples E
+export const LIBCONFIG_800: ModalLibConfig = {
+ slideConfig: {
+ playConfig: {
+ autoPlay: true,
+ interval: 5000,
+ pauseOnHover: true
+ }
+ }
+};
+
+export const LIBCONFIG_801: ModalLibConfig = {
+ slideConfig: {
+ infinite: true,
+ playConfig: {
+ autoPlay: true,
+ interval: 5000,
+ pauseOnHover: false
+ }
+ }
+};
+
+export const LIBCONFIG_802: ModalLibConfig = {
+ slideConfig: {
+ playConfig: {
+ autoPlay: true,
+ interval: 1000,
+ pauseOnHover: false
+ }
+ }
+};
+
+// Examples F
+export const LIBCONFIG_900: ModalLibConfig = {
+ slideConfig: {
+ infinite: false
+ },
+ currentImageConfig: {
+ loadingConfig: { enable: true, type: LoadingType.STANDARD } as LoadingConfig,
+ description: { strategy: DescriptionStrategy.ALWAYS_VISIBLE } as Description
+ }
+};
diff --git a/src/app/modal-gallery/modal-gallery.component.ts b/src/app/modal-gallery/modal-gallery.component.ts
new file mode 100644
index 00000000..716310a4
--- /dev/null
+++ b/src/app/modal-gallery/modal-gallery.component.ts
@@ -0,0 +1,765 @@
+/*
+ The MIT License (MIT)
+
+ Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import { TemplateRef } from '@angular/core';
+import { ViewChild } from '@angular/core';
+import { Component, OnDestroy } from '@angular/core';
+import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
+
+import {
+ Action,
+ ButtonEvent,
+ ButtonType,
+ Image,
+ ImageModalEvent,
+ ModalGalleryService,
+ ModalGalleryRef,
+ ModalGalleryConfig,
+ ModalLibConfig
+} from '@ks89/angular-modal-gallery';
+import { Subscription } from 'rxjs';
+
+import * as libConfigs from './libconfigs';
+import { FormsModule } from '@angular/forms';
+import { NgTemplateOutlet } from '@angular/common';
+
+@Component({
+ selector: 'ks-modal-gallery-page',
+ templateUrl: './modal-gallery.html',
+ styleUrls: ['./modal-gallery.scss'],
+ imports: [FormsModule, NgTemplateOutlet]
+})
+export class ModalGalleryExampleComponent implements OnDestroy {
+ /**
+ * A custom template to illustrate the customization of previews rendering.
+ */
+ @ViewChild('previewsTemplate')
+ previewsTemplate?: TemplateRef;
+
+ imageIndex = 0;
+ galleryId = 1;
+ isPlaying = true;
+ // Examples A
+ CONFIG406: ModalLibConfig = libConfigs.LIBCONFIG_406;
+ CONFIG407: ModalLibConfig = libConfigs.LIBCONFIG_407;
+ CONFIG408: ModalLibConfig = libConfigs.LIBCONFIG_408;
+ // Examples B
+ CONFIG500: ModalLibConfig = libConfigs.LIBCONFIG_500;
+ CONFIG501: ModalLibConfig = libConfigs.LIBCONFIG_501;
+ CONFIG502: ModalLibConfig = libConfigs.LIBCONFIG_502;
+ CONFIG503: ModalLibConfig = libConfigs.LIBCONFIG_503;
+ CONFIG504: ModalLibConfig = libConfigs.LIBCONFIG_504;
+ CONFIG505: ModalLibConfig = libConfigs.LIBCONFIG_505;
+ CONFIG506: ModalLibConfig = libConfigs.LIBCONFIG_506;
+ CONFIG507: ModalLibConfig = libConfigs.LIBCONFIG_507;
+ CONFIG508: ModalLibConfig = libConfigs.LIBCONFIG_508;
+ CONFIG509: ModalLibConfig = libConfigs.LIBCONFIG_509;
+ CONFIG510: ModalLibConfig = libConfigs.LIBCONFIG_510;
+ CONFIG511: ModalLibConfig = libConfigs.LIBCONFIG_511;
+ CONFIG512: ModalLibConfig = libConfigs.LIBCONFIG_512;
+ CONFIG513: ModalLibConfig = libConfigs.LIBCONFIG_513;
+ CONFIG514: ModalLibConfig = libConfigs.LIBCONFIG_514;
+ CONFIG515: ModalLibConfig = libConfigs.LIBCONFIG_515;
+ CONFIG516: ModalLibConfig = libConfigs.LIBCONFIG_516;
+ CONFIG517: ModalLibConfig = libConfigs.LIBCONFIG_517;
+ CONFIG518: ModalLibConfig = libConfigs.LIBCONFIG_518;
+ CONFIG519: ModalLibConfig = libConfigs.LIBCONFIG_519;
+ CONFIG520: ModalLibConfig = libConfigs.LIBCONFIG_520;
+ CONFIG521: ModalLibConfig = libConfigs.LIBCONFIG_521;
+ CONFIG522: ModalLibConfig = libConfigs.LIBCONFIG_522;
+ CONFIG523: ModalLibConfig = libConfigs.LIBCONFIG_523;
+ CONFIG524: ModalLibConfig = libConfigs.LIBCONFIG_524;
+ CONFIG525: ModalLibConfig = libConfigs.LIBCONFIG_525;
+ // Examples C
+ CONFIG600: ModalLibConfig = libConfigs.LIBCONFIG_600;
+ CONFIG601: ModalLibConfig = libConfigs.LIBCONFIG_601;
+ CONFIG602: ModalLibConfig = libConfigs.LIBCONFIG_602;
+ CONFIG603: ModalLibConfig = libConfigs.LIBCONFIG_603;
+ CONFIG604: ModalLibConfig = libConfigs.LIBCONFIG_604;
+ CONFIG605: ModalLibConfig = libConfigs.LIBCONFIG_605;
+ CONFIG606: ModalLibConfig = libConfigs.LIBCONFIG_606;
+ CONFIG607: ModalLibConfig = libConfigs.LIBCONFIG_607;
+ CONFIG608: ModalLibConfig = libConfigs.LIBCONFIG_608;
+ CONFIG609: ModalLibConfig = libConfigs.LIBCONFIG_609;
+ CONFIG610: ModalLibConfig = libConfigs.LIBCONFIG_610;
+ CONFIG611: ModalLibConfig = libConfigs.LIBCONFIG_611;
+ CONFIG612: ModalLibConfig = libConfigs.LIBCONFIG_612;
+ CONFIG613: ModalLibConfig = libConfigs.LIBCONFIG_613;
+ // Examples D
+ CONFIG701: ModalLibConfig = libConfigs.LIBCONFIG_701;
+ CONFIG702: ModalLibConfig = libConfigs.LIBCONFIG_702;
+ CONFIG703: ModalLibConfig = libConfigs.LIBCONFIG_703;
+ // Examples E
+ CONFIG800: ModalLibConfig = libConfigs.LIBCONFIG_800;
+ CONFIG801: ModalLibConfig = libConfigs.LIBCONFIG_801;
+ CONFIG802: ModalLibConfig = libConfigs.LIBCONFIG_802;
+ // Example F
+ CONFIG900: ModalLibConfig = libConfigs.LIBCONFIG_900;
+
+ images: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img2.jpg',
+ description: 'Description 2'
+ }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/img3.jpg',
+ description: 'Description 3',
+ extUrl: 'http://www.google.com'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/img3.png',
+ title: 'custom title 2',
+ alt: 'custom alt 2',
+ ariaLabel: 'arial label 2'
+ }
+ ),
+ new Image(3, {
+ img: '../assets/images/gallery/img4.jpg',
+ description: 'Description 4',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(4, { img: '../assets/images/gallery/img5.jpg' }, { img: '../assets/images/gallery/thumbs/img5.jpg' })
+ ];
+
+ emptyImagesArray: Image[] = [];
+
+ imagesRectNoTitles: Image[] = [
+ new Image(
+ 0,
+ { img: '../assets/images/gallery/milan-pegasus-gallery-statue.jpg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg', title: '' }
+ ),
+ new Image(
+ 1,
+ { img: '../assets/images/gallery/pexels-photo-47223.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-47223.jpg', title: '' }
+ ),
+ new Image(
+ 2,
+ { img: '../assets/images/gallery/pexels-photo-52062.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-52062.jpg', title: '' }
+ ),
+ new Image(
+ 3,
+ { img: '../assets/images/gallery/pexels-photo-66943.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-66943.jpg', title: '' }
+ ),
+ new Image(
+ 4,
+ { img: '../assets/images/gallery/pexels-photo-93750.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-93750.jpg', title: '' }
+ ),
+ new Image(
+ 5,
+ { img: '../assets/images/gallery/pexels-photo-94420.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-94420.jpg', title: '' }
+ ),
+ new Image(
+ 6,
+ { img: '../assets/images/gallery/pexels-photo-96947.jpeg', title: '' },
+ { img: '../assets/images/gallery/thumbs/t-pexels-photo-96947.jpg', title: '' }
+ )
+ ];
+
+ fallbackImages: Image[] = [
+ new Image(0, {
+ // this file is not available so the browser returns an error
+ img: '../assets/images/gallery/UNEXISTING_IMG1.jpg',
+ // because the img above doesn't exists, the library will use this file
+ fallbackImg: '../assets/images/gallery/fallback1.jpg'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/UNEXISTING_IMG2.jpg',
+ fallbackImg: '../assets/images/gallery/fallback2.jpg'
+ }),
+ new Image(
+ 2,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG3.jpg',
+ fallbackImg: '../assets/images/gallery/fallback3.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG3.png',
+ fallbackImg: '../assets/images/gallery/fallback3.jpg'
+ }
+ ),
+ new Image(3, {
+ img: '../assets/images/gallery/UNEXISTING_IMG4.jpg',
+ fallbackImg: '../assets/images/gallery/fallback4.jpg'
+ }),
+ new Image(
+ 4,
+ {
+ img: '../assets/images/gallery/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback5.jpg'
+ },
+ {
+ img: '../assets/images/gallery/thumbs/UNEXISTING_IMG5.jpg',
+ fallbackImg: '../assets/images/gallery/fallback5.jpg'
+ }
+ )
+ ];
+
+ // array of images (obviously with different id) where paths are the same.
+ // to prevent caching issues I have to append '?index'.
+ sameImages: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg?1',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img1.jpg?2',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(2, {
+ img: '../assets/images/gallery/img1.jpg?3',
+ extUrl: 'http://www.google.com'
+ })
+ ];
+
+ // example of a png converted into base64 using https://www.base64-image.de/ or other similar websites
+ base64String =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABN0lEQV' +
+ 'R4nO3SQQ2AQBDAwAVlaMEhCkAV' +
+ 'b2RcQmcU9NEZAAAAAOD/tvN675k5VoewxLOvLmAtA8QZIM4AcQaIM0CcAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4gwQZ4A4A8QZIM4AcQaIM0C' +
+ 'cAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4gwQZ4A4A8QZIM4AcQaIM0CcAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4g' +
+ 'wQZ4A4A8QZIM4AcQaIM0CcAeIMEGeAOAPEGSDOAHEGiDNAnAHiDBBngDgDxBkgzgBxBogzQJwB4gwQZ4A4A8QZIM4AcQaIM0CcAeIMEGeAOAPEGQAAAAAA4Pc+8asEoPPGq' +
+ 'xUAAAAASUVORK5CYII';
+
+ base64RedString =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAY1BMVEX/AAD/////WVn/+vr/qan/Nzf/ERH/2tr/s7P/KSn/' +
+ '7+//vr7/0ND/W1v/6+v/m5v/4+P/U1P/HR3/o6P/rq7/g4P/k5P/t7f/dXX/SEj/zMz/ZWX/h4f/bm7/amr/np7/yMhDG/2oAAAC8ElEQVR4nO3dC3KqQBCF4WkHERHFRyKIL/' +
+ 'a/ymDuVYMMFipTbbfnW8H5S4lQVGUMaWe4B3iHQvlQKB8K5UOhfCiUD4XyoVA+FJ7Myijd5dvBO9nmuzQqZ68X2mI9NO9suC7s84VxNuAO6GSQxU8VJvuQe3pn4T55uLDYcK9+' +
+ '0KZ4qDB574vPbej+HF2Fcc499km563p0FAbcQ18QdCi0B+6VLzk0fjtuC0dj7o0vGo/uF064B/agvFcYca/rRdReeOTe1pNjW6HkP6J1gbtQwzV4NnEVJtyrepU0C2M599ldhH' +
+ 'GjcMq9qWfT28KUe1Hv0nrhnHuPB/Na4YJ7jgeLv4UZ9xovsmuhXXKP8WJpL4Ur7i2erC6Fun4Kr8Jz4Rf3Em++/hdKf+htN/5XqOuGtC75LfzmnuHR96nQ6v2SVl9TWxVq/pKevq' +
+ 'aG1twjvFpXhTLeLz1rQMZyb/DMmhH3BM9GRudjxVVmtN51n62M1DdpXeVG2rveR22MxLe9jxgazfdsJ2Oj9en3THsfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +
+ 'AAAAAAAAAAAAAAgHba/+98+AFnI+g/30L/GSX6z5nRf1aQ/vOe9J/Zpf/cNf1n533A+Yf6z7DUfw6p/rNkVX9Nkw850/kDzuXWf7Y6ab37Xl0K7ZJ7ixdLeykknQ8YGV0LacG9xo' +
+ 'MF/S2cc8/xYF4rpJR7T+9SqhfSlHtRz6Z0Wxjr+lEM40ahstvThJqFNOFe1aMJuQop4N7Vm4DchXTkXtaTI7UVUsS9rRcRtRequBZLuldII+mPw+MR3S8ke+De+JKDvQ1qFMr+kx' +
+ 'o0cxyFFEt945bHjhpXYXV/I/HN8DBxtrgLiQpp74Y3RUtJW2H1Oe7l3IuHe/fnd7+wuh4zGe+lBpnr+utSWLHF+r0vyeG6aPw+PFT4a1ZG6S7fDt7JNt+lUTnrsL5LoWwolA+F8q' +
+ 'FQPhTKh0L5UCgfCuVDoXw/lnQz7dm7GjoAAAAASUVORK5CYII=';
+ base64GreenString =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIAgMAAADQNkYNAAAADFBMVEUAAAAy/ysy/ysy/ysyTcibAAAAA3RSTlMA2r/af0d' +
+ 'WAAAAQUlEQVRo3u3YMREAMAzEsJAMyZJsMXy3XORdBFySJK3qxFXH1Y1DEARBEARBEARBEARBEARBkNmk436mvSRJ0o4eOKL2P81eyn8AAAAASUVORK5CYII=';
+
+ base64Image: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.base64String);
+ base64RedImage: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.base64RedString);
+ base64GreenImage: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.base64GreenString);
+
+ imagesBase64: Image[] = [
+ new Image(0, {
+ img: this.base64Image,
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: this.base64GreenImage,
+ description: 'Description 2'
+ }),
+ new Image(
+ 2,
+ {
+ img: this.base64RedImage,
+ description: 'Description 3',
+ extUrl: 'http://www.google.com'
+ },
+ {
+ img: this.base64RedImage,
+ title: 'custom title 2',
+ alt: 'custom alt 2',
+ ariaLabel: 'arial label 2'
+ }
+ )
+ ];
+
+ imagesCustomDownloadFileName: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg',
+ downloadFileName: 'first-img.jpg'
+ }),
+ new Image(1, {
+ img: this.base64Image,
+ downloadFileName: 'second-img-base64.jpg'
+ })
+ ];
+
+ imagesHtmlDescriptions: Image[] = [
+ new Image(0, {
+ img: '../assets/images/gallery/img1.jpg',
+ extUrl: 'http://www.google.com'
+ }),
+ new Image(1, {
+ img: '../assets/images/gallery/img2.jpg',
+ description: '
C14 - (id=613) - Advanced demo - disable clicks on current image in modal-image
+
+
+
+
+
C15 - (id=614) - Advanced demo - after 3 seconds it closes modal gallery automatically with galleryService close method
+
Attention: please check the console to understand what it's happening!
+
Timer will clear and restart every time you show another image!
+
+
+
+
+
+
+
Other examples
+
+
D1 - (id=700) - Other demo - base64 images
+
+
+
+
+
D2 - (id=701) - Other demo - custom file name, used when downloaded
+
+
+
+
+
D3 - (id=702) - Other demo - invert touchscreen swipe direction
+
+
+
+
+
D4 - (id=703) - Other demo - infinite sliding and automatic add of images when the gallery is visible
+
+
+
+
+
D5 - (id=704) - Other demo - automatic update of the second image (index=1) via gallery service (open the second image and wait some seconds to see the result)
+
+
+
+
+
D6 - (id=705) - Other demo - remove title attribute on images
+
+
+
+
+
D7 - (id=706) - Other demo - modal gallery with an empty images array
+
+
+
+
+
+
+
AutoPlay examples
+
+
E1 - (id=800) - AutoPlay demo - with interval=5000 and pauseOnHover enabled
+
+
+
+
E2 - (id=801) - AutoPlay demo - with interval=5000, pauseOnHover disabled and infinite is enabled
+
+
+
+
E3 - (id=802) - AutoPlay demo - with interval=1000 and pauseOnHover disabled
+
+
+
+
+
+
+
+
+
Experimental examples*
+
* these will be either improved or removed in next versions
+
+
F1 - (id=900) - Experimental demo - infinite sliding with only one image to see if side arrows are hidden
+
+
+
+
F2 - (id=901) - Experimental demo - an array of Images with the same source file (different classes/ids and paths with appended '?imageIndex' to prevent caching issues)
P9 - (id=208) - row plain gallery layout with fallback images when it's not possible to load normal images (to fix issue #194)
+
+
+
+
+
+
P10 - (id=209) - row plain gallery layout with a tags and custom rectangular sizes (limit 4) + fallback images when it's not possible to load normal images (to fix issue #194)
+
+
+
+
+
+
P11 - (id=210) - row plain gallery layout (limit 2) and custom size + remove title attribute on images
+
+
+
+
+
+
P12 - (id=211) - row plain gallery layout with an empty images array
+
+
+
+
+
diff --git a/src/app/plain-gallery/plain-gallery.scss b/src/app/plain-gallery/plain-gallery.scss
new file mode 100644
index 00000000..db327eae
--- /dev/null
+++ b/src/app/plain-gallery/plain-gallery.scss
@@ -0,0 +1,169 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+:host {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+ margin-left: 20px;
+ margin-right: 20px;
+}
+
+section {
+ width: 100%;
+}
+
+$text-color: #fff;
+$background: rgba(0, 0, 0, .7);
+
+button {
+ background-color: #0060b7;
+ color: white;
+ height: 25px;
+ width: auto;
+ border: 0;
+ border-radius: 5px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #0086ff;
+ }
+}
+
+.my-app-custom-plain-container-row {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+
+ .my-app-custom-image-row {
+ cursor: pointer;
+ height: auto;
+ margin-right: 2px;
+ width: 50px;
+
+ &.after {
+ border-top: 2px;
+ cursor: pointer;
+ display: none;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+ }
+}
+
+.my-app-custom-plain-container-column {
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+
+ .my-app-custom-image-column {
+ cursor: pointer;
+ height: auto;
+ margin-right: 2px;
+ width: 50px;
+
+ &.after {
+ border-top: 2px;
+ cursor: pointer;
+ display: none;
+ height: 100%;
+ left: 0;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+ }
+}
+
+.my-app-custom-plain-container-with-desc {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+
+ figure {
+ margin: 0;
+ position: relative;
+
+ img {
+ max-width: 100%;
+ height: auto;
+ cursor: pointer;
+ }
+
+ figcaption {
+ background: rgba(0, 0, 0, .5);
+ color: #fff;
+ font-size: 85%;
+ padding: 5px;
+ position: absolute;
+ bottom: 3px;
+ left: 0;
+ right: 0;
+ }
+ }
+
+ .description {
+ font-weight: bold;
+ text-align: center;
+ }
+
+ .my-app-custom-image-with-desc {
+ height: auto;
+ margin-right: 2px;
+ width: 200px;
+ align-self: start;
+ }
+}
+
+.more {
+ background: $background;
+ cursor: pointer;
+ color: $text-color;
+ padding-top: 4px;
+ height: 46px;
+ position: absolute;
+ text-align: center;
+ width: 50px;
+}
+
+
+.transcluded {
+ color: white;
+ font-weight: 600;
+ font-size: 24px;
+ text-align: center;
+ position: absolute;
+ top: 50%;
+ width: 100%;
+ pointer-events: none;
+}
+
+.title {
+ margin-top: 40px;
+}
diff --git a/src/assets/images/gallery/fallback-carousel1.jpg b/src/assets/images/gallery/fallback-carousel1.jpg
new file mode 100644
index 00000000..5d49263f
Binary files /dev/null and b/src/assets/images/gallery/fallback-carousel1.jpg differ
diff --git a/src/assets/images/gallery/fallback-carousel2.jpg b/src/assets/images/gallery/fallback-carousel2.jpg
new file mode 100644
index 00000000..2c2ce26a
Binary files /dev/null and b/src/assets/images/gallery/fallback-carousel2.jpg differ
diff --git a/src/assets/images/gallery/fallback-carousel3.jpg b/src/assets/images/gallery/fallback-carousel3.jpg
new file mode 100644
index 00000000..a4ea6b40
Binary files /dev/null and b/src/assets/images/gallery/fallback-carousel3.jpg differ
diff --git a/src/assets/images/gallery/fallback-carousel4.jpg b/src/assets/images/gallery/fallback-carousel4.jpg
new file mode 100644
index 00000000..0d59507d
Binary files /dev/null and b/src/assets/images/gallery/fallback-carousel4.jpg differ
diff --git a/src/assets/images/gallery/fallback-carousel5.jpg b/src/assets/images/gallery/fallback-carousel5.jpg
new file mode 100644
index 00000000..5511feb8
Binary files /dev/null and b/src/assets/images/gallery/fallback-carousel5.jpg differ
diff --git a/src/assets/images/gallery/fallback1.jpg b/src/assets/images/gallery/fallback1.jpg
new file mode 100644
index 00000000..0827c782
Binary files /dev/null and b/src/assets/images/gallery/fallback1.jpg differ
diff --git a/src/assets/images/gallery/fallback2.jpg b/src/assets/images/gallery/fallback2.jpg
new file mode 100644
index 00000000..6562c517
Binary files /dev/null and b/src/assets/images/gallery/fallback2.jpg differ
diff --git a/src/assets/images/gallery/fallback3.jpg b/src/assets/images/gallery/fallback3.jpg
new file mode 100644
index 00000000..3a3d63f4
Binary files /dev/null and b/src/assets/images/gallery/fallback3.jpg differ
diff --git a/src/assets/images/gallery/fallback4.jpg b/src/assets/images/gallery/fallback4.jpg
new file mode 100644
index 00000000..0dfe8551
Binary files /dev/null and b/src/assets/images/gallery/fallback4.jpg differ
diff --git a/src/assets/images/gallery/fallback5.jpg b/src/assets/images/gallery/fallback5.jpg
new file mode 100644
index 00000000..38da58e4
Binary files /dev/null and b/src/assets/images/gallery/fallback5.jpg differ
diff --git a/src/assets/images/gallery/img1.jpg b/src/assets/images/gallery/img1.jpg
new file mode 100644
index 00000000..7d358899
Binary files /dev/null and b/src/assets/images/gallery/img1.jpg differ
diff --git a/src/assets/images/gallery/img2.jpg b/src/assets/images/gallery/img2.jpg
new file mode 100644
index 00000000..4c101c12
Binary files /dev/null and b/src/assets/images/gallery/img2.jpg differ
diff --git a/src/assets/images/gallery/img2.png b/src/assets/images/gallery/img2.png
new file mode 100644
index 00000000..295cb1f4
Binary files /dev/null and b/src/assets/images/gallery/img2.png differ
diff --git a/src/assets/images/gallery/img3.jpg b/src/assets/images/gallery/img3.jpg
new file mode 100644
index 00000000..c6b1c22d
Binary files /dev/null and b/src/assets/images/gallery/img3.jpg differ
diff --git a/src/assets/images/gallery/img4.jpg b/src/assets/images/gallery/img4.jpg
new file mode 100644
index 00000000..403482ae
Binary files /dev/null and b/src/assets/images/gallery/img4.jpg differ
diff --git a/src/assets/images/gallery/img5.jpg b/src/assets/images/gallery/img5.jpg
new file mode 100644
index 00000000..bf97ab6c
Binary files /dev/null and b/src/assets/images/gallery/img5.jpg differ
diff --git a/src/assets/images/gallery/milan-pegasus-gallery-statue-1024w.jpg b/src/assets/images/gallery/milan-pegasus-gallery-statue-1024w.jpg
new file mode 100644
index 00000000..c0a922d1
Binary files /dev/null and b/src/assets/images/gallery/milan-pegasus-gallery-statue-1024w.jpg differ
diff --git a/src/assets/images/gallery/milan-pegasus-gallery-statue-480w.jpg b/src/assets/images/gallery/milan-pegasus-gallery-statue-480w.jpg
new file mode 100644
index 00000000..ac54d7d7
Binary files /dev/null and b/src/assets/images/gallery/milan-pegasus-gallery-statue-480w.jpg differ
diff --git a/src/assets/images/gallery/milan-pegasus-gallery-statue-768w.jpg b/src/assets/images/gallery/milan-pegasus-gallery-statue-768w.jpg
new file mode 100644
index 00000000..dba50163
Binary files /dev/null and b/src/assets/images/gallery/milan-pegasus-gallery-statue-768w.jpg differ
diff --git a/src/assets/images/gallery/milan-pegasus-gallery-statue.jpg b/src/assets/images/gallery/milan-pegasus-gallery-statue.jpg
new file mode 100644
index 00000000..f2c02760
Binary files /dev/null and b/src/assets/images/gallery/milan-pegasus-gallery-statue.jpg differ
diff --git a/src/assets/images/gallery/pexels-photo-135230-1024w.png b/src/assets/images/gallery/pexels-photo-135230-1024w.png
new file mode 100644
index 00000000..a543d4db
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-135230-1024w.png differ
diff --git a/src/assets/images/gallery/pexels-photo-135230-480w.png b/src/assets/images/gallery/pexels-photo-135230-480w.png
new file mode 100644
index 00000000..dc29041e
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-135230-480w.png differ
diff --git a/src/assets/images/gallery/pexels-photo-135230-768w.png b/src/assets/images/gallery/pexels-photo-135230-768w.png
new file mode 100644
index 00000000..b04917ed
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-135230-768w.png differ
diff --git a/src/assets/images/gallery/pexels-photo-135230.png b/src/assets/images/gallery/pexels-photo-135230.png
new file mode 100644
index 00000000..8aa887fd
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-135230.png differ
diff --git a/src/assets/images/gallery/pexels-photo-47223-1024w.jpeg b/src/assets/images/gallery/pexels-photo-47223-1024w.jpeg
new file mode 100644
index 00000000..f7931c21
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-47223-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-47223-480w.jpeg b/src/assets/images/gallery/pexels-photo-47223-480w.jpeg
new file mode 100644
index 00000000..14897ecf
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-47223-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-47223-768w.jpeg b/src/assets/images/gallery/pexels-photo-47223-768w.jpeg
new file mode 100644
index 00000000..73f72c9a
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-47223-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-47223.jpeg b/src/assets/images/gallery/pexels-photo-47223.jpeg
new file mode 100644
index 00000000..46ea1f75
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-47223.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-52062-1024w.jpeg b/src/assets/images/gallery/pexels-photo-52062-1024w.jpeg
new file mode 100644
index 00000000..a7052fd3
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-52062-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-52062-480w.jpeg b/src/assets/images/gallery/pexels-photo-52062-480w.jpeg
new file mode 100644
index 00000000..1ac7ce8c
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-52062-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-52062-768w.jpeg b/src/assets/images/gallery/pexels-photo-52062-768w.jpeg
new file mode 100644
index 00000000..e1f3256c
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-52062-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-52062.jpeg b/src/assets/images/gallery/pexels-photo-52062.jpeg
new file mode 100644
index 00000000..0c9502e4
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-52062.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-547115-1024w.jpeg b/src/assets/images/gallery/pexels-photo-547115-1024w.jpeg
new file mode 100644
index 00000000..592a1516
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-547115-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-547115-480w.jpeg b/src/assets/images/gallery/pexels-photo-547115-480w.jpeg
new file mode 100644
index 00000000..15c404dd
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-547115-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-547115-768w.jpeg b/src/assets/images/gallery/pexels-photo-547115-768w.jpeg
new file mode 100644
index 00000000..68fa15da
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-547115-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-547115.jpeg b/src/assets/images/gallery/pexels-photo-547115.jpeg
new file mode 100644
index 00000000..4056b197
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-547115.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-556664-1024w.jpeg b/src/assets/images/gallery/pexels-photo-556664-1024w.jpeg
new file mode 100644
index 00000000..b56cf006
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-556664-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-556664-480w.jpeg b/src/assets/images/gallery/pexels-photo-556664-480w.jpeg
new file mode 100644
index 00000000..f7990e34
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-556664-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-556664-768w.jpeg b/src/assets/images/gallery/pexels-photo-556664-768w.jpeg
new file mode 100644
index 00000000..cbb74582
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-556664-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-556664.jpeg b/src/assets/images/gallery/pexels-photo-556664.jpeg
new file mode 100644
index 00000000..33383d36
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-556664.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-66943-1024w.jpeg b/src/assets/images/gallery/pexels-photo-66943-1024w.jpeg
new file mode 100644
index 00000000..18988bf3
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-66943-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-66943-480w.jpeg b/src/assets/images/gallery/pexels-photo-66943-480w.jpeg
new file mode 100644
index 00000000..c40829b9
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-66943-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-66943-768w.jpeg b/src/assets/images/gallery/pexels-photo-66943-768w.jpeg
new file mode 100644
index 00000000..953ef6f3
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-66943-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-66943.jpeg b/src/assets/images/gallery/pexels-photo-66943.jpeg
new file mode 100644
index 00000000..8dd20157
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-66943.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-787594-1024w.jpeg b/src/assets/images/gallery/pexels-photo-787594-1024w.jpeg
new file mode 100644
index 00000000..52113fef
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-787594-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-787594-480w.jpeg b/src/assets/images/gallery/pexels-photo-787594-480w.jpeg
new file mode 100644
index 00000000..3b97fb45
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-787594-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-787594-768w.jpeg b/src/assets/images/gallery/pexels-photo-787594-768w.jpeg
new file mode 100644
index 00000000..b08cc133
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-787594-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-787594.jpeg b/src/assets/images/gallery/pexels-photo-787594.jpeg
new file mode 100644
index 00000000..e2986131
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-787594.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-803105-1024w.jpeg b/src/assets/images/gallery/pexels-photo-803105-1024w.jpeg
new file mode 100644
index 00000000..99bb60cd
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-803105-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-803105-480w.jpeg b/src/assets/images/gallery/pexels-photo-803105-480w.jpeg
new file mode 100644
index 00000000..815624dc
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-803105-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-803105-768w.jpeg b/src/assets/images/gallery/pexels-photo-803105-768w.jpeg
new file mode 100644
index 00000000..1118cde8
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-803105-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-803105.jpeg b/src/assets/images/gallery/pexels-photo-803105.jpeg
new file mode 100644
index 00000000..8b7c44a4
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-803105.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-93750-1024w.jpeg b/src/assets/images/gallery/pexels-photo-93750-1024w.jpeg
new file mode 100644
index 00000000..bf1e8eaf
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-93750-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-93750-480w.jpeg b/src/assets/images/gallery/pexels-photo-93750-480w.jpeg
new file mode 100644
index 00000000..08020dbe
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-93750-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-93750-768w.jpeg b/src/assets/images/gallery/pexels-photo-93750-768w.jpeg
new file mode 100644
index 00000000..8fe37d40
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-93750-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-93750.jpeg b/src/assets/images/gallery/pexels-photo-93750.jpeg
new file mode 100644
index 00000000..ab537b02
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-93750.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-94420-1024w.jpeg b/src/assets/images/gallery/pexels-photo-94420-1024w.jpeg
new file mode 100644
index 00000000..c8a13148
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-94420-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-94420-480w.jpeg b/src/assets/images/gallery/pexels-photo-94420-480w.jpeg
new file mode 100644
index 00000000..acc790e7
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-94420-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-94420-768w.jpeg b/src/assets/images/gallery/pexels-photo-94420-768w.jpeg
new file mode 100644
index 00000000..11fbaad9
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-94420-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-94420.jpeg b/src/assets/images/gallery/pexels-photo-94420.jpeg
new file mode 100644
index 00000000..d3ebb012
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-94420.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-96947-1024w.jpeg b/src/assets/images/gallery/pexels-photo-96947-1024w.jpeg
new file mode 100644
index 00000000..3e4a701f
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-96947-1024w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-96947-480w.jpeg b/src/assets/images/gallery/pexels-photo-96947-480w.jpeg
new file mode 100644
index 00000000..0248070f
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-96947-480w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-96947-768w.jpeg b/src/assets/images/gallery/pexels-photo-96947-768w.jpeg
new file mode 100644
index 00000000..c74f523e
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-96947-768w.jpeg differ
diff --git a/src/assets/images/gallery/pexels-photo-96947.jpeg b/src/assets/images/gallery/pexels-photo-96947.jpeg
new file mode 100644
index 00000000..c6965748
Binary files /dev/null and b/src/assets/images/gallery/pexels-photo-96947.jpeg differ
diff --git a/src/assets/images/gallery/thumbs/fallback-carousel1.jpg b/src/assets/images/gallery/thumbs/fallback-carousel1.jpg
new file mode 100644
index 00000000..5ce2a4ed
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback-carousel1.jpg differ
diff --git a/src/assets/images/gallery/thumbs/fallback-carousel2.jpg b/src/assets/images/gallery/thumbs/fallback-carousel2.jpg
new file mode 100644
index 00000000..6417a30b
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback-carousel2.jpg differ
diff --git a/src/assets/images/gallery/thumbs/fallback-carousel3.jpg b/src/assets/images/gallery/thumbs/fallback-carousel3.jpg
new file mode 100644
index 00000000..a0939a7a
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback-carousel3.jpg differ
diff --git a/src/assets/images/gallery/thumbs/fallback-carousel4.jpg b/src/assets/images/gallery/thumbs/fallback-carousel4.jpg
new file mode 100644
index 00000000..5be118b7
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback-carousel4.jpg differ
diff --git a/src/assets/images/gallery/thumbs/fallback-carousel5.jpg b/src/assets/images/gallery/thumbs/fallback-carousel5.jpg
new file mode 100644
index 00000000..c61eb0ee
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback-carousel5.jpg differ
diff --git a/src/assets/images/gallery/thumbs/fallback1.jpg b/src/assets/images/gallery/thumbs/fallback1.jpg
new file mode 100644
index 00000000..2afb5530
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback1.jpg differ
diff --git a/src/assets/images/gallery/thumbs/fallback2.jpg b/src/assets/images/gallery/thumbs/fallback2.jpg
new file mode 100644
index 00000000..140e9718
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback2.jpg differ
diff --git a/src/assets/images/gallery/thumbs/fallback3.jpg b/src/assets/images/gallery/thumbs/fallback3.jpg
new file mode 100644
index 00000000..11115387
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback3.jpg differ
diff --git a/src/assets/images/gallery/thumbs/fallback4.jpg b/src/assets/images/gallery/thumbs/fallback4.jpg
new file mode 100644
index 00000000..9737793b
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback4.jpg differ
diff --git a/src/assets/images/gallery/thumbs/fallback5.jpg b/src/assets/images/gallery/thumbs/fallback5.jpg
new file mode 100644
index 00000000..88915773
Binary files /dev/null and b/src/assets/images/gallery/thumbs/fallback5.jpg differ
diff --git a/src/assets/images/gallery/thumbs/img1.jpg b/src/assets/images/gallery/thumbs/img1.jpg
new file mode 100644
index 00000000..68e10d14
Binary files /dev/null and b/src/assets/images/gallery/thumbs/img1.jpg differ
diff --git a/src/assets/images/gallery/thumbs/img2.jpg b/src/assets/images/gallery/thumbs/img2.jpg
new file mode 100644
index 00000000..3b69746a
Binary files /dev/null and b/src/assets/images/gallery/thumbs/img2.jpg differ
diff --git a/src/assets/images/gallery/thumbs/img3.png b/src/assets/images/gallery/thumbs/img3.png
new file mode 100644
index 00000000..3f0bf060
Binary files /dev/null and b/src/assets/images/gallery/thumbs/img3.png differ
diff --git a/src/assets/images/gallery/thumbs/img4.jpg b/src/assets/images/gallery/thumbs/img4.jpg
new file mode 100644
index 00000000..d08ebe7f
Binary files /dev/null and b/src/assets/images/gallery/thumbs/img4.jpg differ
diff --git a/src/assets/images/gallery/thumbs/img5.jpg b/src/assets/images/gallery/thumbs/img5.jpg
new file mode 100644
index 00000000..b932ee9f
Binary files /dev/null and b/src/assets/images/gallery/thumbs/img5.jpg differ
diff --git a/src/assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg b/src/assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg
new file mode 100644
index 00000000..6881e08c
Binary files /dev/null and b/src/assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg differ
diff --git a/src/assets/images/gallery/thumbs/t-pexels-photo-47223.jpg b/src/assets/images/gallery/thumbs/t-pexels-photo-47223.jpg
new file mode 100644
index 00000000..be93d546
Binary files /dev/null and b/src/assets/images/gallery/thumbs/t-pexels-photo-47223.jpg differ
diff --git a/src/assets/images/gallery/thumbs/t-pexels-photo-52062.jpg b/src/assets/images/gallery/thumbs/t-pexels-photo-52062.jpg
new file mode 100644
index 00000000..eb99fe42
Binary files /dev/null and b/src/assets/images/gallery/thumbs/t-pexels-photo-52062.jpg differ
diff --git a/src/assets/images/gallery/thumbs/t-pexels-photo-66943.jpg b/src/assets/images/gallery/thumbs/t-pexels-photo-66943.jpg
new file mode 100644
index 00000000..e33dc4be
Binary files /dev/null and b/src/assets/images/gallery/thumbs/t-pexels-photo-66943.jpg differ
diff --git a/src/assets/images/gallery/thumbs/t-pexels-photo-93750.jpg b/src/assets/images/gallery/thumbs/t-pexels-photo-93750.jpg
new file mode 100644
index 00000000..feae47af
Binary files /dev/null and b/src/assets/images/gallery/thumbs/t-pexels-photo-93750.jpg differ
diff --git a/src/assets/images/gallery/thumbs/t-pexels-photo-94420.jpg b/src/assets/images/gallery/thumbs/t-pexels-photo-94420.jpg
new file mode 100644
index 00000000..075cdd51
Binary files /dev/null and b/src/assets/images/gallery/thumbs/t-pexels-photo-94420.jpg differ
diff --git a/src/assets/images/gallery/thumbs/t-pexels-photo-96947.jpg b/src/assets/images/gallery/thumbs/t-pexels-photo-96947.jpg
new file mode 100644
index 00000000..ab777557
Binary files /dev/null and b/src/assets/images/gallery/thumbs/t-pexels-photo-96947.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/pexels-photo-106006-thumb.jpg b/src/assets/images/loading-spinner-samples/pexels-photo-106006-thumb.jpg
new file mode 100644
index 00000000..e80e1a53
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/pexels-photo-106006-thumb.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/pexels-photo-106006.jpg b/src/assets/images/loading-spinner-samples/pexels-photo-106006.jpg
new file mode 100644
index 00000000..6baa8325
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/pexels-photo-106006.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/pexels-photo-464336-thumb.jpg b/src/assets/images/loading-spinner-samples/pexels-photo-464336-thumb.jpg
new file mode 100644
index 00000000..7bd64447
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/pexels-photo-464336-thumb.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/pexels-photo-464336.jpg b/src/assets/images/loading-spinner-samples/pexels-photo-464336.jpg
new file mode 100644
index 00000000..c7253d57
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/pexels-photo-464336.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/pexels-photo-74506-thumb.jpg b/src/assets/images/loading-spinner-samples/pexels-photo-74506-thumb.jpg
new file mode 100644
index 00000000..24028aa0
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/pexels-photo-74506-thumb.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/pexels-photo-74506.jpg b/src/assets/images/loading-spinner-samples/pexels-photo-74506.jpg
new file mode 100644
index 00000000..c4973505
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/pexels-photo-74506.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/pexels-photo-thumb.jpg b/src/assets/images/loading-spinner-samples/pexels-photo-thumb.jpg
new file mode 100644
index 00000000..34615206
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/pexels-photo-thumb.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/pexels-photo.jpg b/src/assets/images/loading-spinner-samples/pexels-photo.jpg
new file mode 100644
index 00000000..3ceffcbe
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/pexels-photo.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/traffic-highway-lights-night-56891-thumb.jpg b/src/assets/images/loading-spinner-samples/traffic-highway-lights-night-56891-thumb.jpg
new file mode 100644
index 00000000..8cfda5fa
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/traffic-highway-lights-night-56891-thumb.jpg differ
diff --git a/src/assets/images/loading-spinner-samples/traffic-highway-lights-night-56891.jpg b/src/assets/images/loading-spinner-samples/traffic-highway-lights-night-56891.jpg
new file mode 100644
index 00000000..9457c1c6
Binary files /dev/null and b/src/assets/images/loading-spinner-samples/traffic-highway-lights-night-56891.jpg differ
diff --git a/src/assets/menu.svg b/src/assets/menu.svg
new file mode 100644
index 00000000..65fc9003
--- /dev/null
+++ b/src/assets/menu.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/favicon.png b/src/favicon.png
new file mode 100644
index 00000000..4e279875
Binary files /dev/null and b/src/favicon.png differ
diff --git a/src/index.html b/src/index.html
new file mode 100644
index 00000000..692b241f
--- /dev/null
+++ b/src/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+ @ks89/angular-modal-gallery demo
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 00000000..8a45c41a
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,7 @@
+import { bootstrapApplication } from '@angular/platform-browser';
+
+import { appConfig } from './app/app.config';
+import { AppComponent } from './app/app.component';
+
+bootstrapApplication(AppComponent, appConfig)
+ .catch((err) => console.error(err));
diff --git a/src/styles.scss b/src/styles.scss
new file mode 100644
index 00000000..51566981
--- /dev/null
+++ b/src/styles.scss
@@ -0,0 +1,61 @@
+// The MIT License (MIT)
+//
+// Copyright (c) 2017-2025 Stefano Cappa (Ks89)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+// You can add global styles to this file, and also import other style files
+
+// *****************************************************************
+// *********** required by @ks89/angular-modal-gallery *************
+// *****************************************************************
+// ATTENTION: You have to install @angular/cdk to be able to use @ks89/angular-modal-gallery properly
+@use "@angular/cdk/overlay-prebuilt.css" as *;
+
+.ks-modal-gallery-backdrop {
+ background: #000 !important;;
+ opacity: 0.85 !important;;
+}
+
+.ks-modal-gallery-panel {
+ z-index: 90000 !important;
+}
+// *****************************************************************
+// *****************************************************************
+// *****************************************************************
+
+body {
+ font-family: "Montserrat", sans-serif;
+ margin: 0;
+ padding: 0;
+}
+
+// Not required by angular-modal-gallery. Used only in this demo
+.red-text {
+ color: red;
+}
+
+.title {
+ text-align: center;
+ color: red;
+}
+
+.center-text {
+ text-align: center;
+}
diff --git a/src/test.ts b/src/test.ts
new file mode 100644
index 00000000..c04c8760
--- /dev/null
+++ b/src/test.ts
@@ -0,0 +1,26 @@
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+
+import 'zone.js/testing';
+import { getTestBed } from '@angular/core/testing';
+import {
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+declare const require: {
+ context(path: string, deep?: boolean, filter?: RegExp): {
+ (id: string): T;
+ keys(): string[];
+ };
+};
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting(),
+);
+
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().forEach(context);
diff --git a/systemjs.config.js b/systemjs.config.js
deleted file mode 100644
index 2e12bf6a..00000000
--- a/systemjs.config.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * System configuration for Angular 2 samples
- * Adjust as necessary for your application needs.
- */
-(function(global) {
- // map tells the System loader where to look for things
- var map = {
- 'app': '/app', // 'dist',
-// 'httpresource': 'node_modules/httpresource',
- '@angular': 'node_modules/@angular',
- 'rxjs': 'node_modules/rxjs',
- 'directives' : 'directives/'
- };
- // packages tells the System loader how to load when no filename and/or no extension
- var packages = {
- 'app': { main: 'main.js', defaultExtension: 'js' },
- 'rxjs': { defaultExtension: 'js' },
- 'directives':{ defaultExtension: 'js' }
- };
- var ngPackageNames = [
- 'common',
- 'compiler',
- 'core',
- 'platform-browser',
- 'platform-browser-dynamic',
- 'upgrade'
- ];
- // Individual files (~300 requests):
- function packIndex(pkgName) {
- packages['@angular/'+pkgName] = { main: 'index.js', defaultExtension: 'js' };
- }
- // Bundled (~40 requests):
- function packUmd(pkgName) {
- packages['@angular/'+pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' };
- }
- // Most environments should use UMD; some (Karma) need the individual index files
- var setPackageConfig = System.packageWithIndex ? packIndex : packUmd;
-// packages['httpresource']={ default: 'resource.js',defaultExtension: 'js' };
- // Add package entries for angular packages
- ngPackageNames.forEach(setPackageConfig);
- var config = {
- map: map,
- packages: packages
- };
- System.config(config);
-})(this);
diff --git a/tsconfig.app.json b/tsconfig.app.json
new file mode 100644
index 00000000..264f459b
--- /dev/null
+++ b/tsconfig.app.json
@@ -0,0 +1,15 @@
+/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
+/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./out-tsc/app",
+ "types": []
+ },
+ "include": [
+ "src/**/*.ts"
+ ],
+ "exclude": [
+ "src/**/*.spec.ts"
+ ]
+}
diff --git a/tsconfig.json b/tsconfig.json
index 9be71e4c..2bce877d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,17 +1,50 @@
+/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
+/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
+ "compileOnSave": false,
"compilerOptions": {
- "target": "es5",
- "module": "system",
- "moduleResolution": "node",
- "sourceMap": true,
- "emitDecoratorMetadata": true,
+ "paths": {
+ "@ks89/angular-modal-gallery": [
+ "./dist/ks89/angular-modal-gallery"
+ ]
+ },
+ "strict": true,
+ "noImplicitOverride": true,
+ "noPropertyAccessFromIndexSignature": false, // FIXME should become tre
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "skipLibCheck": true,
+ "isolatedModules": true,
"experimentalDecorators": true,
- "removeComments": false,
- "noImplicitAny": false
+ "importHelpers": true,
+ "target": "ES2022",
+ "module": "preserve",
+ "useDefineForClassFields": false, // to inject sanitizer without errors
+ "typeRoots": [
+ "node_modules/@types"
+ ]
},
- "exclude": [
- "node_modules",
- "typings/main",
- "typings/main.d.ts"
+ "angularCompilerOptions": {
+ "enableI18nLegacyMessageIdFormat": false,
+ "strictInjectionParameters": true,
+ "strictInputAccessModifiers": true,
+ "typeCheckHostBindings": true,
+ // FIXME THIS SHOULD BE FORCED TO TRUE IN THE FUTURE VERSIONS OF THE LIBRARY
+ "strictTemplates": false
+ },
+ "files": [],
+ "references": [
+ {
+ "path": "./tsconfig.app.json"
+ },
+ {
+ "path": "./tsconfig.spec.json"
+ },
+ {
+ "path": "./projects/ks89/angular-modal-gallery/tsconfig.lib.json"
+ },
+ {
+ "path": "./projects/ks89/angular-modal-gallery/tsconfig.spec.json"
+ }
]
}
diff --git a/tsconfig.spec.json b/tsconfig.spec.json
new file mode 100644
index 00000000..04df34cf
--- /dev/null
+++ b/tsconfig.spec.json
@@ -0,0 +1,14 @@
+/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
+/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./out-tsc/spec",
+ "types": [
+ "jasmine"
+ ]
+ },
+ "include": [
+ "src/**/*.ts"
+ ]
+}
diff --git a/typings.json b/typings.json
deleted file mode 100644
index c06bb4d7..00000000
--- a/typings.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "ambientDependencies": {
- "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#7de6c3dd94feaeb21f20054b9f30d5dabc5efabd",
- "jasmine": "github:DefinitelyTyped/DefinitelyTyped/jasmine/jasmine.d.ts#7de6c3dd94feaeb21f20054b9f30d5dabc5efabd"
- }
-}