Full Code of MurhafSousli/ngx-gallery for AI

master c03dfbe894f3 cached
208 files
454.5 KB
141.4k tokens
248 symbols
1 requests
Download .txt
Showing preview only (514K chars total). Download the full file or copy to clipboard to get everything.
Repository: MurhafSousli/ngx-gallery
Branch: master
Commit: c03dfbe894f3
Files: 208
Total size: 454.5 KB

Directory structure:
gitextract_ao06cvmc/

├── .editorconfig
├── .eslintrc.json
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── feature_request.md
│   │   └── question.md
│   └── workflows/
│       ├── integrate.yml
│       └── netlify.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── angular.json
├── package.json
├── projects/
│   ├── ng-gallery/
│   │   ├── .eslintrc.json
│   │   ├── .storybook/
│   │   │   ├── colors.mdx
│   │   │   ├── main.ts
│   │   │   ├── manager.js
│   │   │   ├── preview.ts
│   │   │   ├── theme.js
│   │   │   ├── tsconfig.json
│   │   │   └── typings.d.ts
│   │   ├── README.md
│   │   ├── karma.conf.js
│   │   ├── lightbox/
│   │   │   ├── ng-package.json
│   │   │   └── src/
│   │   │       ├── gallerize.directive.ts
│   │   │       ├── lightbox.animation.ts
│   │   │       ├── lightbox.component.scss
│   │   │       ├── lightbox.component.ts
│   │   │       ├── lightbox.default.ts
│   │   │       ├── lightbox.directive.ts
│   │   │       ├── lightbox.model.ts
│   │   │       ├── lightbox.module.ts
│   │   │       ├── lightbox.service.ts
│   │   │       └── public_api.ts
│   │   ├── ng-package.json
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── lib/
│   │   │   │   ├── auto-height/
│   │   │   │   │   ├── auto-height.spec.ts
│   │   │   │   │   └── auto-height.ts
│   │   │   │   ├── autoplay/
│   │   │   │   │   └── autoplay.directive.ts
│   │   │   │   ├── bullets/
│   │   │   │   │   ├── gallery-bullets.component.ts
│   │   │   │   │   ├── gallery-bullets.scss
│   │   │   │   │   └── gallery-bullets.spec.ts
│   │   │   │   ├── core/
│   │   │   │   │   ├── gallery.component.ts
│   │   │   │   │   ├── gallery.scss
│   │   │   │   │   └── gallery.spec.ts
│   │   │   │   ├── counter/
│   │   │   │   │   ├── gallery-counter.component.ts
│   │   │   │   │   ├── gallery-counter.scss
│   │   │   │   │   └── gallery-counter.spec.ts
│   │   │   │   ├── debug/
│   │   │   │   │   └── debug.scss
│   │   │   │   ├── directives/
│   │   │   │   │   ├── gallery-box-def.directive.spec.ts
│   │   │   │   │   ├── gallery-box-def.directive.ts
│   │   │   │   │   ├── gallery-item-def.directive.spec.ts
│   │   │   │   │   └── gallery-item-def.directive.ts
│   │   │   │   ├── gallery.module.ts
│   │   │   │   ├── gestures/
│   │   │   │   │   ├── hammer-slider.spec.ts
│   │   │   │   │   ├── hammer-sliding.directive.ts
│   │   │   │   │   └── mouse-sliding.directive.ts
│   │   │   │   ├── models/
│   │   │   │   │   ├── config.model.ts
│   │   │   │   │   ├── constants.ts
│   │   │   │   │   ├── item.model.ts
│   │   │   │   │   ├── slider.model.ts
│   │   │   │   │   └── styles.model.ts
│   │   │   │   ├── nav/
│   │   │   │   │   ├── gallery-nav.component.ts
│   │   │   │   │   ├── gallery-nav.scss
│   │   │   │   │   └── gallery-nav.spec.ts
│   │   │   │   ├── observers/
│   │   │   │   │   ├── intersection-directive.spec.ts
│   │   │   │   │   ├── intersection-observer.ts
│   │   │   │   │   └── intersection-sensor.directive.ts
│   │   │   │   ├── services/
│   │   │   │   │   ├── gallery-ref.ts
│   │   │   │   │   ├── hammer.ts
│   │   │   │   │   ├── resize-directive.spec.ts
│   │   │   │   │   ├── resize-sensor.ts
│   │   │   │   │   ├── scroll-snap-type.spec.ts
│   │   │   │   │   └── scroll-snap-type.ts
│   │   │   │   ├── slider/
│   │   │   │   │   ├── adapters/
│   │   │   │   │   │   ├── base-adapter.ts
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   ├── main-adapter.spec.ts
│   │   │   │   │   │   └── main-adapters.ts
│   │   │   │   │   ├── gallery-slider.component.ts
│   │   │   │   │   ├── gallery-slider.scss
│   │   │   │   │   ├── slider/
│   │   │   │   │   │   ├── slider.spec.ts
│   │   │   │   │   │   └── slider.ts
│   │   │   │   │   └── slider-item/
│   │   │   │   │       ├── slider-item.scss
│   │   │   │   │       ├── slider-item.spec.ts
│   │   │   │   │       └── slider-item.ts
│   │   │   │   ├── smooth-scroll/
│   │   │   │   │   ├── bezier-easing.spec.ts
│   │   │   │   │   ├── bezier-easing.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── smooth-scroll.directive.ts
│   │   │   │   │   ├── smooth-scroll.model.ts
│   │   │   │   │   └── smooth-scroll.spec.ts
│   │   │   │   ├── templates/
│   │   │   │   │   ├── gallery-iframe.component.ts
│   │   │   │   │   ├── gallery-image.component.spec.ts
│   │   │   │   │   ├── gallery-image.component.ts
│   │   │   │   │   ├── gallery-image.scss
│   │   │   │   │   ├── gallery-video.component.ts
│   │   │   │   │   ├── items.model.ts
│   │   │   │   │   └── svg-assets.ts
│   │   │   │   ├── tests/
│   │   │   │   │   ├── common.ts
│   │   │   │   │   └── test-images.ts
│   │   │   │   ├── thumbs/
│   │   │   │   │   ├── gallery-thumbs.component.ts
│   │   │   │   │   ├── gallery-thumbs.scss
│   │   │   │   │   └── gallery-thumbs.spec.ts
│   │   │   │   └── utils/
│   │   │   │       ├── gallery.default.ts
│   │   │   │       ├── img-manager.spec.ts
│   │   │   │       ├── img-manager.ts
│   │   │   │       ├── img-recognizer.spec.ts
│   │   │   │       ├── img-recognizer.ts
│   │   │   │       ├── item.class.spec.ts
│   │   │   │       └── item.class.ts
│   │   │   ├── public-api.ts
│   │   │   ├── stories/
│   │   │   │   ├── .eslintrc.json
│   │   │   │   ├── GettingStarted.mdx
│   │   │   │   ├── LoadItems.mdx
│   │   │   │   ├── Responsiveness.mdx
│   │   │   │   ├── basic/
│   │   │   │   │   ├── Bullets.mdx
│   │   │   │   │   ├── Counter.mdx
│   │   │   │   │   ├── Gallery.stories.ts
│   │   │   │   │   ├── Navigation.mdx
│   │   │   │   │   ├── Player.mdx
│   │   │   │   │   ├── Slider.mdx
│   │   │   │   │   └── Thumbnails.mdx
│   │   │   │   ├── custom-templates/
│   │   │   │   │   ├── CustomTemplates.stories.ts
│   │   │   │   │   ├── CustomTemplatesUsage.mdx
│   │   │   │   │   └── custom-template.component.ts
│   │   │   │   ├── lightbox/
│   │   │   │   │   ├── CustomTemplates.mdx
│   │   │   │   │   ├── Gallerize.mdx
│   │   │   │   │   ├── GettingStarted.mdx
│   │   │   │   │   ├── Lightbox.mdx
│   │   │   │   │   ├── Lightbox.stories.ts
│   │   │   │   │   └── lightbox-example.ts
│   │   │   │   └── pixabay/
│   │   │   │       ├── pixabay.model.ts
│   │   │   │       └── pixabay.service.ts
│   │   │   └── test.ts
│   │   ├── tsconfig.lib.json
│   │   ├── tsconfig.lib.prod.json
│   │   └── tsconfig.spec.json
│   └── ng-gallery-demo/
│       ├── public/
│       │   └── icons/
│       │       ├── browserconfig.xml
│       │       └── site.webmanifest.json
│       ├── src/
│       │   ├── app/
│       │   │   ├── app-routing.animations.ts
│       │   │   ├── app.component.html
│       │   │   ├── app.component.scss
│       │   │   ├── app.component.ts
│       │   │   ├── app.config.server.ts
│       │   │   ├── app.config.ts
│       │   │   ├── app.routes.ts
│       │   │   ├── pages/
│       │   │   │   ├── documentation/
│       │   │   │   │   ├── doc-core/
│       │   │   │   │   │   ├── doc-core.component.html
│       │   │   │   │   │   ├── doc-core.component.scss
│       │   │   │   │   │   └── doc-core.component.ts
│       │   │   │   │   ├── doc-lightbox/
│       │   │   │   │   │   ├── doc-lightbox.component.html
│       │   │   │   │   │   ├── doc-lightbox.component.scss
│       │   │   │   │   │   └── doc-lightbox.component.ts
│       │   │   │   │   ├── documentation.component.html
│       │   │   │   │   ├── documentation.component.scss
│       │   │   │   │   ├── documentation.component.ts
│       │   │   │   │   └── routes.ts
│       │   │   │   ├── examples/
│       │   │   │   │   └── basic-example/
│       │   │   │   │       └── basic-example.ts
│       │   │   │   ├── gallerize-example/
│       │   │   │   │   ├── gallerize-example.component.html
│       │   │   │   │   ├── gallerize-example.component.scss
│       │   │   │   │   └── gallerize-example.component.ts
│       │   │   │   ├── gallery-example/
│       │   │   │   │   ├── gallery-example.component.html
│       │   │   │   │   ├── gallery-example.component.scss
│       │   │   │   │   └── gallery-example.component.ts
│       │   │   │   ├── home/
│       │   │   │   │   ├── home.component.html
│       │   │   │   │   ├── home.component.scss
│       │   │   │   │   └── home.component.ts
│       │   │   │   ├── lab/
│       │   │   │   │   ├── lab.component.html
│       │   │   │   │   ├── lab.component.scss
│       │   │   │   │   └── lab.component.ts
│       │   │   │   ├── lightbox-example/
│       │   │   │   │   ├── lightbox-example.component.html
│       │   │   │   │   ├── lightbox-example.component.scss
│       │   │   │   │   └── lightbox-example.component.ts
│       │   │   │   ├── not-found/
│       │   │   │   │   ├── not-found.component.html
│       │   │   │   │   ├── not-found.component.scss
│       │   │   │   │   └── not-found.component.ts
│       │   │   │   └── templates-example/
│       │   │   │       ├── slide-text.animation.ts
│       │   │   │       ├── templates-example.component.html
│       │   │   │       ├── templates-example.component.scss
│       │   │   │       └── templates-example.component.ts
│       │   │   ├── service/
│       │   │   │   ├── pixabay.model.ts
│       │   │   │   └── pixabay.service.ts
│       │   │   └── shared/
│       │   │       ├── badges/
│       │   │       │   ├── badges.component.html
│       │   │       │   └── badges.component.ts
│       │   │       ├── footer/
│       │   │       │   ├── footer.component.html
│       │   │       │   ├── footer.component.scss
│       │   │       │   └── footer.component.ts
│       │   │       ├── gallery-mock-dialog.ts
│       │   │       ├── hl-code/
│       │   │       │   ├── hl-code.component.html
│       │   │       │   ├── hl-code.component.scss
│       │   │       │   └── hl-code.component.ts
│       │   │       ├── menu/
│       │   │       │   ├── menu.component.html
│       │   │       │   ├── menu.component.scss
│       │   │       │   └── menu.component.ts
│       │   │       ├── note/
│       │   │       │   ├── note.component.scss
│       │   │       │   └── note.component.ts
│       │   │       ├── pipes/
│       │   │       │   └── keys.pipe.ts
│       │   │       └── section-title/
│       │   │           ├── section-title.component.scss
│       │   │           └── section-title.component.ts
│       │   ├── index.html
│       │   ├── main.server.ts
│       │   ├── main.ts
│       │   ├── mixin.scss
│       │   ├── server.ts
│       │   ├── styles.scss
│       │   └── theme.scss
│       ├── tsconfig.app.json
│       └── tsconfig.spec.json
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# Editor configuration, see https://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.ts]
quote_type = single

[*.md]
max_line_length = off
trim_trailing_whitespace = false


================================================
FILE: .eslintrc.json
================================================
{
  "root": true,
  "ignorePatterns": [
    "projects/**/*"
  ],
  "overrides": [
    {
      "files": [
        "*.ts"
      ],
      "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:@angular-eslint/recommended",
        "plugin:@angular-eslint/template/process-inline-templates"
      ],
      "rules": {
        "@angular-eslint/directive-selector": [
          "error",
          {
            "type": "attribute",
            "prefix": "app",
            "style": "camelCase"
          }
        ],
        "@angular-eslint/component-selector": [
          "error",
          {
            "type": "element",
            "prefix": "app",
            "style": "kebab-case"
          }
        ]
      }
    },
    {
      "files": [
        "*.html"
      ],
      "extends": [
        "plugin:@angular-eslint/template/recommended",
        "plugin:@angular-eslint/template/accessibility"
      ],
      "rules": {}
    }
  ]
}


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve

---

<!-- 
1. Please make sure that you have searched in the older issues before submitting a new one!
2. Please fill out all the required information!
 -->


#### What is the expected behavior?


#### What is the current behavior?


#### What are the steps to reproduce?

<!-- 
Providing a StackBlitz reproduction is the *best* way to share your issue. <br/>
StackBlitz starter: https://stackblitz.com/edit/ngx-gallery<br/>
-->


#### What is the use-case or motivation for changing an existing behavior?



#### Which versions are you using for the following packages?

Angular:
Angular CDK:
Angular CLI:
Typescript:
Gallery: 


#### Is there anything else we should know?


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project

---

<!-- 
1. Please make sure that you have searched in the older issues before submitting a new one!
2. Please fill out all the required information!
 -->


#### What is the expected behavior?


#### What is the current behavior?


#### What are the steps to reproduce?

<!-- 
Providing a StackBlitz reproduction is the *best* way to share your issue. <br/>
StackBlitz starter: https://stackblitz.com/edit/ngx-gallery<br/>
-->


#### What is the use-case or motivation for changing an existing behavior?



#### Which versions are you using for the following packages?

Angular:
Angular CDK:
Angular CLI:
Typescript:
Gallery: 


#### Is there anything else we should know?


================================================
FILE: .github/ISSUE_TEMPLATE/question.md
================================================
---
name: Question
about: Ask a question

---

<!-- 
1. Please make sure that you have searched in the older issues before submitting a new one!
2. Please fill out all the required information!
 -->


#### What is the expected behavior?


#### What is the current behavior?


#### What are the steps to reproduce?

<!-- 
Providing a StackBlitz reproduction is the *best* way to share your issue. <br/>
StackBlitz starter: https://stackblitz.com/edit/ngx-gallery<br/>
-->


#### What is the use-case or motivation for changing an existing behavior?



#### Which versions are you using for the following packages?

Angular:
Angular CDK:
Angular CLI:
Typescript:
Gallery: 


#### Is there anything else we should know?


================================================
FILE: .github/workflows/integrate.yml
================================================
name: CI Build

on:
  pull_request:
    branches: [ master ]
  push:
    branches: [ master ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@master

      - name: Use Node.js 20
        uses: actions/setup-node@master
        with:
          node-version: 20

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build-lib

#      - name: Lint
#        run: npm run lint-lib

      - name: Test
        run: npm run test-lib-headless

      - name: Upload coverage reports to Codecov
        uses: codecov/codecov-action@main
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          slug: MurhafSousli/ngx-gallery

      - name: Code Coverage Report
        uses: irongut/CodeCoverageSummary@master
        with:
          filename: coverage/**/cobertura-coverage.xml
          badge: true
          fail_below_min: true
          format: markdown
          hide_branch_rate: false
          hide_complexity: true
          indicators: true
          output: both
          thresholds: '60 80'

      - name: Add Coverage PR Comment
        uses: marocchino/sticky-pull-request-comment@main
        if: github.event_name == 'pull_request'
        with:
          recreate: true
          path: code-coverage-results.md
        continue-on-error: true  # Allow this step to fail


================================================
FILE: .github/workflows/netlify.yml
================================================
name: Deploy Demo

on:
  push:
    branches: [ deploy-netlify ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Use Node.js 20
        uses: actions/setup-node@v1
        with:
          node-version: 20
      - name: Install dependencies
        run: npm ci
      - name: Build demo
        run: npm run build-demo
      - name: Deploy to Netlify
        uses: nwtgck/actions-netlify@v1.1.11
        with:
          publish-dir: './dist/ng-gallery-demo'
          production-branch: deploy-netlify
          github-token: ${{ secrets.GITHUB_TOKEN }}
          deploy-message: "Deploy from GitHub Actions"
          enable-pull-request-comment: true
          enable-commit-comment: true
          overwrites-pull-request-comment: true
        env:
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
        timeout-minutes: 1


================================================
FILE: .gitignore
================================================
# See http://help.github.com/ignore-files/ for more about ignoring files.

# Compiled output
/dist
/tmp
/out-tsc
/bazel-out

# Node
/node_modules
npm-debug.log
yarn-error.log

# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*

# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings

# System files
.DS_Store
Thumbs.db


================================================
FILE: CHANGELOG.md
================================================
# Changelog

## 13.0.0

 - Use native dialog to display the lightbox instead of Angular CDK overlay.  

## 12.0.0-beta.5

- feat: Add `provideGalleryOptions` and `provideLightboxOptions` to set global options.
- refactor: Use `useFactory` function to set the default options for `GALLERY_CONFIG` and `LIGHTBOX_CONFIG` token.

## 12.0.0

- Add vimeo support in [#575](https://github.com/MurhafSousli/ngx-gallery/pull/575).

## 12.0.0-beta.4

> See the [storybook documentation](https://ngx-gallery-next.netlify.app/)

- feat: Add RTL support, closes [#540](https://github.com/MurhafSousli/ngx-gallery/issues/540).
- All boolean inputs of `<gallery>` components can be used as string attributes
  - e.g. `<gallery autoHeight>`, `<gallery autoHeight="true">` and `<gallery [autoHeight]="true">` sets the option's value to true.
  - e.g. `<gallery autoHeight="false">` and `<gallery [autoHeight]="false">` sets the option's value to false.
- All number inputs of `<gallery>` components can be used as string attributes
  - e.g. `<gallery playerInterval="2000">` and `<gallery [playerInterval]="2000">` sets the option's value to 2000

**Improved performance**

- refactor: Replace the scroll event with intersection observer to detect the active item while scrolling.

**ItemAutoSize, ThumbAutoSize features**

- enhance: Toggling `itemAutoSize` option is now reactive.
- fix: `[thumbAutosize]` causes random invalid starting thumbnail scroller position when scrolling possible, closes [#521](https://github.com/MurhafSousli/ngx-gallery/issues/521)
- fix: `[ItemAutosize]` in website/safari browsers do not work as expected, closes [#543](https://github.com/MurhafSousli/ngx-gallery/issues/543)

**AutoHeight feature**

- enhance: Auto-height feature is not more precise and works well with or without height transition
- fix: Auto-height issue when screen size changes

**Autoplay feature**

- fix: `autoplay` resets the timer after navigated.
- fix: `autoplay` only start the timer after the image is loaded.

**Bullets (previously named 'Dots')**

- feat: `disableBullets` disable bullets' clicks

**Custom template**

- feature: Introduce `galleryImage` directive within `galleryItemDef`, to allow recognizing the img element in your custom item template.

### Breaking changes

#### Options renamed:

**Core**
- `slidingDirection` → `orientation`
- `slidingEase` → `scrollEase`
- `slidingDuration` → `scrollDuration`
- `slidingDisabled` → `disableScroll`
- `mouseSlidingDisabled` → `disableMouseScroll`
- `autoPlay` → `autoplay`

**Thumbs**
- `thumb` → `thumbs`
- `thumbMode` → `thumbCentralized`
- `thumbMode` → `thumbCentralized`
- `thumbDetached` → `detachThumbs`
- `thumbSlidingDisabled` → `disableThumbMouseScroll`
- `thumbMouseSlidingDisabled` → `disableThumbMouseScroll`

**Bullets**
- `dots` → `bullets`
- `dotSize` → `bulletSize`
- `dotPosition` → `bulletPosition`

#### Input removed (no longer exist)

- `navScrollBehavior` the option is now removed, use `scrollBehavior` instead.


***

## 11.0.0

- feat: Add `galleryThumbDef`, `galleryImageDef`, `galleryItemDef`, `galleryBoxDef` to set custom templates, closes [#487](https://github.com/MurhafSousli/ngx-gallery/issues/487).
- feat: Add `imageTemplate` property to `GalleryConfig`.
- feat: Add `args` property in case need to attach extra data with the gallery item.
- enhance: Improve overall typings.

### Breaking changes

- Usage of setting custom template has been changed! see the [wiki page](https://github.com/MurhafSousli/ngx-gallery/wiki) for more info.
- The inputs `itemTemplate`, `thumbTemplate` and `boxTemplate` has been removed from the gallery component, however they still exist in `GalleryConfig`


## 10.0.0

- feat: Migrate to standalone components.

### Breaking Changes

- Both `GalleryModule` and `LightboxModule` no longer provide the `withConfig()` method.

## 9.0.1

- Remove `bezier-easing` package from dependencies, closes [#525](https://github.com/MurhafSousli/ngx-gallery/issues/525) and [#551](https://github.com/MurhafSousli/ngx-gallery/issues/551) in [6c47ecb](https://github.com/MurhafSousli/ngx-gallery/pull/556/commits/6c47ecb59185909186f10a9860d1a98b326ad2d0).

## 9.0.0

- Upgrade to Angular 16

## 8.0.4

- fix(core): Fix `VideoItem` typo, closes [#529](https://github.com/MurhafSousli/ngx-gallery/issues/529).
- enhance(lightbox): Allow multiple classes for `backdropPanel` and `panelClass` config, closes [31368ed](https://github.com/MurhafSousli/ngx-gallery/pull/542/commits/b9d36e31cbc05d258db21bdcfb857aaa70900ba3) and [#541](https://github.com/MurhafSousli/ngx-gallery/issues/541).

## 8.0.3

- fix(core): SSR error, closes [532](https://github.com/MurhafSousli/ngx-gallery/issues/532) in [#533](https://github.com/MurhafSousli/ngx-gallery/pull/533).

## 8.0.2

- feat(core): Add `loadingAttr` option to img and iframe elements, closes [#513](https://github.com/MurhafSousli/ngx-gallery/issues/513) in [093789b](https://github.com/MurhafSousli/ngx-gallery/pull/517/commits/093789b42cff86da957a09632c96705d42820085).
- fix(core): Videos are unplayable if type not specified, closes [#515](https://github.com/MurhafSousli/ngx-gallery/issues/515) in [11ba153](https://github.com/MurhafSousli/ngx-gallery/pull/516/commits/11ba153401c9c87f2983b99b047cf57849399117).

## 8.0.1

- fix(core): Gallery nav icons are not alignment properly, in [d4dca8b](https://github.com/MurhafSousli/ngx-gallery/pull/506/commits/d4dca8b4f68b4439471ed7f90ea4e23c27ff0c41).
- fox(core): Gallery dots is not horizontally centralized, in [f2d6910](https://github.com/MurhafSousli/ngx-gallery/pull/506/commits/f2d691083350adb64ea4b9c205e6fa3c31f6c0a3).
- fix(core): Fix lib's angular peerDependencies version to >=15 in [9ea5ea3](https://github.com/MurhafSousli/ngx-gallery/pull/506/commits/9ea5ea34f77e6329f9385c7db056cc637557b1bc).

## 8.0.0

- feat(core): Add `isActive` to custom gallery template context, in [0b3f8bf](https://github.com/MurhafSousli/ngx-gallery/pull/497/commits/0b3f8bf43e383a8ee3e53a3140e18b8bcf1c2d69).
- refactor(core): Fix the iframe error regarding the `allow` attribute.
- refactor(core): Change default navigation icons.
- refactor(core): Change default dots size.
- refactor(core): Change default counter styles.
- regret(core): Remove`itemLoaded` output.


## 8.0.0-beta.5

- regret(core): Remove`contentVisibilityAuto` option for version 8.
- ~~feat(core): Add `itemLoaded` output which emits after an item is loaded, for image items it emits after the image is loaded.~~
- feat(core): Add `autoHeight` option, when set to true, the gallery height will fit the active item height.
- feat(core): Add `autoItemSize` option, when set to true, the item will fit its image aspect ratio.
- feat(core): Add `autoThumbSize` option, when set to true, the thumb will fit its image aspect ratio.
- feat(core): Add `scrollBehavior` option.
- feat(core): Add `navScrollBehavior` option.
- feat(core): Add `thumbImageSize` option.
- feat(core): Add more options to the video item.
- feat(core): Add `configSnapshot` to `GalleryRef` class.
- feat(core): Add an optional parameter `behavior` to all `next(behavior?)`, `prev(behavior?)`, `set(index, behavior?)` functions, fallbacks to the `scrollBehavior` config.
- refactor(core): Only display custom item template container when `itemTemplate` is provided.

## 8.0.0-beta.4

- ~~feat(core): Add `contentVisibilityAuto` option to set the proper `content-visibility` and `contain-intrinsic-size` value on all gallery items/thumbs, in [73b20a9](https://github.com/MurhafSousli/ngx-gallery/pull/491/commits/73b20a9f996371e4a3ad52283b358263fd88546f).~~
- feat(core): Use native `loading` attribute on all `img` and `iframe` for native lazy loading.
- refactor(core): Fix loop issue when sliding with using the mouse, in [1572bea](https://github.com/MurhafSousli/ngx-gallery/pull/491/commits/1572beae2bc58792fac94243f4f3e20c0a61e549).
- refactor(core): Remove `lazy-image` directive.

## 8.0.0-beta.3

- fix(core): Set current index in sliding event does not work if slider size number has fraction, in [58e89fb](https://github.com/MurhafSousli/ngx-gallery/pull/491/commits/58e89fb3ed0e3837ca08a8111d567d992717ba7a).

## 8.0.0-beta.2

- fix(lightbox): close button is not displayed, in [506249b](https://github.com/MurhafSousli/ngx-gallery/pull/490/commits/506249bbc5877cd4ed54cf610a42b3a31abcb417).

## 8.0.0-beta.1

- feat(core): Use scrolling slider instead of transform method, allows touchpad scroll to slide the gallery as well as native sliding on mobile browser.
- feat(core): Add `resizeDebounceTime` option which is used to update the gallery on window resize event in [34a2723](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/34a27238eed6515fd748c43909c3fd0098b6a575) and [d867630](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/d86763042553a340b3a367c56c0a2549b690f751).
- feat(core): Add `slidingEase` and `slidingDuration` to customize sliding ease and duration in [4c1db03](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/4c1db03afb3a27896cd6dad5d08f91612bfa75a2).
- feat(core): Add `thumbDetached` to detach thumb slider, closes [#289](https://github.com/MurhafSousli/ngx-gallery/issues/289) in [1f47484](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/1f474847f999b2f1b0c820d50bf7336bfe6b8a3c).
- feat(core): Add `boxTemplate` to the gallery, closes [#487](https://github.com/MurhafSousli/ngx-gallery/issues/487) in [f46e33a](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/f46e33abf231ab86bd0b906e4b20e02e4bbf7bf4).
- feat(core): Add `slidingDisabled` and `thumbSlidingDisabled` options to enable/disable sliding on Mobile and Desktop using the touchpad. in [2451581](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/2451581cee382a17f3a95671cf3950910a5122d3).
- feat(core): Add `mouseSlidingDisabled` and `mouseThumbSlidingDisabled` options to enable/disable sliding on Desktop using the mouse. in [2451581](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/2451581cee382a17f3a95671cf3950910a5122d3).
- feat(core): Images not shown, when number of images get larger, closes [#484](https://github.com/MurhafSousli/ngx-gallery/issues/484) in [e819ebe](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/e819ebe3442f0cd37c1914bccf82a72d1fb2ce85).
- fix(core): `thumbView="contain"` Sliding thumbnails using gestures has an issue, closes [#417](https://github.com/MurhafSousli/ngx-gallery/issues/417) in [e819ebe](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/e819ebe3442f0cd37c1914bccf82a72d1fb2ce85).
- refactor(core): Remove `thumbMode` option from the gallery, in [18f71e3](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/18f71e3599f7af1a20f1482307a6370c0c8b6f05)
- refactor(core): Remove `tapClick` event and use native `click` event, in [3d960cc](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/3d960cca3b4954c999e10a86b0993d45d5c8462f).
- refactor(core): Remove `ng-content` from the gallery, in [63e3b6b](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/63e3b6b5110ee5eaab6452cf775c3488023bd7d9).
- refactor(core): Remove `panSensitivity` option, in [d1f8d34](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/d1f8d342a597d88afe66681b4ae099362273e03c).
- refactor(core): Remove `gestures` option, in [70cb00c](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/70cb00c5aa808ea33aba1b674666ab0f99501a9d).
- refactor(core): Remove `reserveGesturesAction` option, in [4b07fc7](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/4b07fc7ca4ef3d50e59accfe11593ee3a84ee706).
- refactor(core): Remove `zoomOut` option, in [19ba2b8](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/19ba2b8c8d4402085c0b400b3e4c0e014d2e5abb).

### Breaking changes:

HammerJs is only used for sliding using the mouse on desktop only, Sliding on mobile devices is now native scroll.

- `gestures` option has been deprecated.
- `thumbMode` option has been deprecated, sliding thumbnails is free.
- `zoomOut` has been deprecated.
- `reserveGesturesAction` has been deprecated.
- `panSensitivity` has been deprecated.
- Remove `ng-content` from the gallery, use `boxTemplate` option to add your custom layer.
- The default value for `loadingStrategy` option has changed to `LoadingStrategy.Preload`.
- Added new dependency `bezier-easing`.


## 7.1.2

- fix(core): Fix `reserveGestureAction` input and its default value in the lightbox, in [ba95036](https://github.com/MurhafSousli/ngx-gallery/pull/481/commits/ba950362b3fd5378d929ef14b56b1cb602382c9e).
- fix(core): Update gallery sliding position properly on window resize, in [f786d0a](https://github.com/MurhafSousli/ngx-gallery/pull/481/commits/f786d0a1ef44fd4a1c0a89126a57765131b5beba).
- fix(core): Slide bug with touch scroll with gallery thumbnails, related to [#465](https://github.com/MurhafSousli/ngx-gallery/issues/465) in [eb1e60c](https://github.com/MurhafSousli/ngx-gallery/pull/476/commits/eb1e60cd033d939485f4b2e4be30d95b49a3d7c5) in [a26d63f](https://github.com/MurhafSousli/ngx-gallery/pull/481/commits/a26d63fe2f04667eb6bd7e36aba960c066f95305).

## 7.1.1

- feat(core): New option `reserveGesturesAction` adds the ability to block the scrolling of page when sliding the gallery on mobile browser, closes [#477](https://github.com/MurhafSousli/ngx-gallery/issues/477) in [c105f21](https://github.com/MurhafSousli/ngx-gallery/pull/478/commits/c105f21fec747a71e849994d4e749f6e0c4b8b3f).
- fix(core): Slide bug with touch scroll, closes [#465](https://github.com/MurhafSousli/ngx-gallery/issues/465) in [eb1e60c](https://github.com/MurhafSousli/ngx-gallery/pull/476/commits/eb1e60cd033d939485f4b2e4be30d95b49a3d7c5).

## 7.1.0

- feat(core): Settings change shouldn't require restart, closes [#466](https://github.com/MurhafSousli/ngx-gallery/issues/466) in [ee71e52](https://github.com/MurhafSousli/ngx-gallery/pull/472/commits/ee71e528313f7780d82226aa1d849a6debc74ade).
- fix(core): Unable to drag thumbnails properly, closes [473](https://github.com/MurhafSousli/ngx-gallery/issues/473) in [8635701](https://github.com/MurhafSousli/ngx-gallery/pull/472/commits/86357014d140360cb110adb1d3115836c988e4c7).
- fix(core): Setting gestures to false breaks the gallery, closes [#464](https://github.com/MurhafSousli/ngx-gallery/issues/464), [#467](https://github.com/MurhafSousli/ngx-gallery/issues/467) and [#469](https://github.com/MurhafSousli/ngx-gallery/issues/469) in [a374603](https://github.com/MurhafSousli/ngx-gallery/pull/472/commits/a374603d60e82518c5b9de11309e68be365c94bc).
- fix(core): Set `alt` on gallery thumbnails and add `role="button"` to gallery nav, closes [#468](https://github.com/MurhafSousli/ngx-gallery/issues/468) in [57bebca](https://github.com/MurhafSousli/ngx-gallery/pull/471/commits/57bebca657b160e3973fd1f73daf6b655dd25fa2).
- fix(lightbox): Close icon in lightbox container being re-rendered all the time due to bug in template, closes [#307](https://github.com/MurhafSousli/ngx-gallery/issues/307) in [6bdc7a4](https://github.com/MurhafSousli/ngx-gallery/pull/472/commits/6bdc7a4add557a90043a665b73ea77f0d7d1979f).
- enhance(core, lightbox): Remove deprecated usage, in [23506eb](https://github.com/MurhafSousli/ngx-gallery/pull/472/commits/23506eb52ebd2c2e5f1d124e77dc8c8695a7eafc).

## 7.0.4

- fix(core): imageSize option when set to contain, in [3ecf94e](https://github.com/MurhafSousli/ngx-gallery/pull/462/commits/3ecf94e78d26378cc1330f2d432b59675526f63f).

## 7.0.3

- enhance(core): Use `img` element instead of div background image to display images, in [b6b5120](https://github.com/MurhafSousli/ngx-gallery/pull/460/commits/b6b512012a983699446d03481cb39f9739e1e67b).
- feat(core): Add alt property to `GalleryImage`, in [b6b5120](https://github.com/MurhafSousli/ngx-gallery/pull/460/commits/b6b512012a983699446d03481cb39f9739e1e67b).

## 7.0.2

- fix(core): Should not show sliding effect on initial state, closes [#458](https://github.com/MurhafSousli/ngx-gallery/issues/458) in [c810039](https://github.com/MurhafSousli/ngx-gallery/pull/459/commits/c8100396e711b76cf350d69e706e472f63658209).

## 7.0.1

- feat(core): Avoid triggering change detection while dragging in [8ed5948](https://github.com/MurhafSousli/ngx-gallery/pull/456/commits/8ed5948b7e6a12624bb398ce6a70536190563778).
- fix(core): Gallery thumbs vertical slider does not navigate when direction is up, closes [#454](https://github.com/MurhafSousli/ngx-gallery/issues/454) in [4eb7d2f](https://github.com/MurhafSousli/ngx-gallery/pull/455/commits/4eb7d2fda10f5b3f10a049fa955377b3afb392fd).

## 7.0.0

- Update to Angular 14 in [64d5620](https://github.com/MurhafSousli/ngx-gallery/pull/444/commits/64d5620c27ee7ea3caab8ad1cafc9eca7f0c7bf4).

## 6.0.1

- fix: Downgrade rxjs peerDependencies to v6 in [35f58fd](https://github.com/MurhafSousli/ngx-gallery/pull/429/commits/35f58fde087fa4f01916eb4dfd3ff6a10f9c62cc).

## 6.0.0

- Update to Angular 13, closes [#424](https://github.com/MurhafSousli/ngx-gallery/issues/322) in [#420](https://github.com/MurhafSousli/ngx-gallery/pull/420).

## 5.1.1

Adds a new option to the global config as well as an input called `thumbView` which is expects a value of either `default` or `contain`

- feat(core): Thumbnails should always use the available space, closes [#340](https://github.com/MurhafSousli/ngx-gallery/issues/340) in [ad3e514](https://github.com/MurhafSousli/ngx-gallery/pull/409/commits/ad3e5143e86c922d5d5927bb527f83e9a99612d8) and [5523b2e](https://github.com/MurhafSousli/ngx-gallery/pull/411/commits/5523b2e01c0a9263141854bb8b838d85ebf16cc9).
- feat(core): Add `stateSnapshot` property to `GalleryRef` to get an instant snapshot of the gallery state observable.
- fix(core): Video item displays the application instead of video, closes [#398](https://github.com/MurhafSousli/ngx-gallery/issues/398) in [f07c304](https://github.com/MurhafSousli/ngx-gallery/pull/409/commits/f07c3047a2426293bc34bcf57765642df0bcc35a).
- fix(core): Issue with gallery.remove() function, closes [#405](https://github.com/MurhafSousli/ngx-gallery/issues/405) in [f0fb1c3](https://github.com/MurhafSousli/ngx-gallery/pull/411#:~:text=removing%20an%20item-,f0fb1c3,-Merge%20state) and [6e080b4](https://github.com/MurhafSousli/ngx-gallery/pull/411/commits/6e080b43e03448ce329253918ae51c2da56eafae).

 > Special thanks to @NexGenUA for his PR

## 5.0.0

- Upgrade to Angular 10.
- feat(Lightbox): Add start/exit animation options, closes [#346](https://github.com/MurhafSousli/ngx-gallery/issues/346) in [6799a1c](https://github.com/MurhafSousli/ngx-gallery/pull/326/commits/6799a1c5e895dbc0753dfbb9f068d02807adf358).
- feat(video item): ability to disable video controls, in [f6b48b1](https://github.com/MurhafSousli/ngx-gallery/pull/326/commits/f6b48b13616d8999bc5c76bf44a6fa428191ce8b).
- feat(video item, youtube item): Ability to autoplay, closes [#304](https://github.com/MurhafSousli/ngx-gallery/issues/304) in [9caf8bf](https://github.com/MurhafSousli/ngx-gallery/pull/326/commits/9caf8bf7cbc8bf7cf580368cc5e3aefeb7d6daf5).
- feat(youtube item): Allow url parameters, closes [#302](https://github.com/MurhafSousli/ngx-gallery/issues/302) in [3760789](https://github.com/MurhafSousli/ngx-gallery/pull/326/commits/37607895da4266b2ed4c297609db99f710d92b00).
- fix(lightbox): Remove cdk styles import from the library.
- fix(video item): Cannot read property 'nativeElement' of undefined, closes [#353](https://github.com/MurhafSousli/ngx-gallery/issues/353) in [ff10363](https://github.com/MurhafSousli/ngx-gallery/pull/326/commits/ff10363e03090d8dfc88fd44674b3cb655399c58).
- fix(video item): Sanitized urls not working in <video>, closes [#346](https://github.com/MurhafSousli/ngx-gallery/issues/346) in [6799a1c](https://github.com/MurhafSousli/ngx-gallery/pull/326/commits/6799a1c5e895dbc0753dfbb9f068d02807adf358).

## 5.0.0-beta.1

- Upgrade to Angular 9.
- Combine all packages in one package.
- Remove import `HttpClientModule` from `GalleryModule`.

### Breaking changes

**Before:**

- The packages were published on `@ngx-gallery/core`, `@ngx-gallery/lightbox` and `@ngx-gallery/gallerize`.

**After:**

- All the packages are now combined in `ng-gallery` (NOTE: it is not `ngx-gallery` that is a different package).
- Import `GalleryMlodule` from `ng-gallery` and `LightboxModule` from `ng-gallery/lightbox`.
- The module `GallerizeModule` has been removed, the `[gallerize]` directive can still be used from the `LightboxModule`.

## 5.0.0-beta.0

- regret: Remove `mode=intermediate` option, closes [#309](https://github.com/MurhafSousli/ngx-gallery/issues/309) and [#297](https://github.com/MurhafSousli/ngx-gallery/issues/297) in [b1df18c](https://github.com/MurhafSousli/ngx-gallery/pull/311/commits/b1df18c6069ddabe02c0bee1e0653dd97692a96e).

### Breaking changes (There could be more breaking changes until version 5 is released which will make it compatible with Angular 9 and ivy)

- The `loadingMode` option has been removed from the gallery component's input and from the global options.

## 4.0.3

- fix(core): Fix universal error, closes [#262](https://github.com/MurhafSousli/ngx-gallery/issues/262) in [fc6c3f7](https://github.com/MurhafSousli/ngx-gallery/pull/282/commits/fc6c3f76730f90721bee99a2404a39310cccd0db).
- fix(core): clean up state subject in `<gallery-image>` component in [7796b50](https://github.com/MurhafSousli/ngx-gallery/pull/283/commits/7796b500b814bd43564e2284b2a937c9d0ec2229). 

## 4.0.2-beta.0

- feat(core): Add `thumbLoadingIcon` and `thumbLoadingError` to gallery config, in [3f8cdca](https://github.com/MurhafSousli/ngx-gallery/pull/241/commits/3f8cdca3fc84f0eef509a5c88ca3d2b5097966bf) and [f2cae92](https://github.com/MurhafSousli/ngx-gallery/pull/241/commits/f2cae9258eef599ca7821c1fb3a82179d4cf6fba).
- refactor(core): refactor the if/else logic in gallery image template, in [f7d6a22](https://github.com/MurhafSousli/ngx-gallery/pull/247/commits/f7d6a22e3cc20c0977554f8d249bef6aa2076fa5).

## 4.0.1

- **fix(core):** Encapsulate the cache interceptor to gallery images only, closes [#237](https://github.com/MurhafSousli/ngx-gallery/issues/237) in [4616eec](https://github.com/MurhafSousli/ngx-gallery/pull/239/commits/4616eeca239d46d49995adb73ea5cae849ae3de4).

## 4.0.0

- **feat(gallerize):** Scan `imageSrc` and `thumbSrc` attributes for image sources, in [4826d52](https://github.com/MurhafSousli/ngx-gallery/pull/235/commits/4826d52cbaa349a4004d63f39677b436e1fe6496).
- **enhance(core, lightbox, gallerize)**: Ability to lazy load the library.
- **enhance(core, lightbox):** Export `GALLERY_CONFIG` and `LIGHTBOX_CONFIG` tokens, in [56c704f](https://github.com/MurhafSousli/ngx-gallery/pull/235/commits/56c704f7db55d87143e949ebdfe115efca852ae9) and [9cab04b](https://github.com/MurhafSousli/ngx-gallery/pull/235/commits/9cab04ba97f916b16d9c0becd060ec0f127bdbc2).
- **fix(core, lightbox):** Add `Optional()` on injected config in gallery and lightbox services, closes [#234](https://github.com/MurhafSousli/ngx-gallery/issues/234) in [31624f9](https://github.com/MurhafSousli/ngx-gallery/pull/235/commits/31624f998ede9fef4335bb170b69b3d54d846d11).
- **refactor(core, lightbox):** Rename `forRoot(config?)` to `withConfig(config)`, in [8446c1a](https://github.com/MurhafSousli/ngx-gallery/pull/235/commits/8446c1ae1417210698a244be6d30be81fa1eed88).
- **refactor(lightbox):** Remove `providedIn: 'root'` from `Lightbox` service and provid it locally in its module, in [7ba8dd9](https://github.com/MurhafSousli/ngx-gallery/pull/235/commits/7ba8dd9c539559d036e57fb19f01f067367970e5).

### Breaking Changes

- The function `forRoot()` has been removed from `GalleryModule` and `LightboxModule`.
- Use `GalleryModule.withConfig({ ... })` to set config that applies on a module and its children (same applies on `LightboxModule`).
- To set global config across the entire app while still lazy load the library, provide the `GALLERY_CONFIG` token with the config value in the root module (same applies on `LightboxModule` with `LIGHTBOX_CONFIG`).

##### Example: Lazy load the library

In this example, will set global config without importing the library in the main bundle

- Provide `GALLERY_CONFIG` value in the root module

```ts
import { GALLERY_CONFIG } from '@ngx-gallery/core';

@NgModule({
  providers: [
    {
      provide: GALLERY_CONFIG, 
      useValue: {
        dots: true,
        imageSize: 'cover'
      }
    }
  ]
})
export class AppModule { }
```

- Import `GalleryModule` in a feature module

```ts
import { GalleryModule } from '@ngx-gallery/core';

@NgModule({
  imports: [
    GalleryModule
  ]
})
export class FeatureModule { }
```


## 4.0.0-beta.1

- **feat(core):** Add indeterminate option to the radial progress, in [df682c4](https://github.com/MurhafSousli/ngx-gallery/pull/233/commits/df682c4353f3795dd3f45f53dfa488b428fdb99f).
- **enhance(core):** Enhance thumbnails loading styles, in [f34f90a](https://github.com/MurhafSousli/ngx-gallery/pull/233/commits/f34f90a542aa437fa12b996dc77f6b7dd9fd819c).
- **fix(core):** Expose `[dotSize]`, `[bulletsPosition]` and `[counterPosition]` options as inputs, in [946a856](https://github.com/MurhafSousli/ngx-gallery/pull/233/commits/946a85618acdc91692183f8f65765bbd137815cc).
- **fix(core):** Add `[loadingMode]` option to gallery images which accepts `determinate` or `indeterminate` , in [e8bdfb2](https://github.com/MurhafSousli/ngx-gallery/pull/233/commits/e8bdfb2a28d485e3d89290dda1edb595ae3efecf).
- **regression(core):** Fix undisplayed thumb image when a custom thumb template is used, in [34f2cc6](https://github.com/MurhafSousli/ngx-gallery/pull/233/commits/34f2cc6f7f5316a929a1efc2ceb6bde8d42d0551).
- **enhance(gallerize):** Run gallerize detector outside angular zone, makes opening the lightbox smoother, in [284925d](https://github.com/MurhafSousli/ngx-gallery/pull/233/commits/284925dd53a67e6a2a2d0b208a30623783db37bc) and [98901b9](https://github.com/MurhafSousli/ngx-gallery/pull/233/commits/98901b9136b4405a5d549fed08a36ec654279a26).

## 4.0.0-beta.0

- **update(core, lightbox, gallerize)**: Update peer dependencies, closes [#228](https://github.com/MurhafSousli/ngx-gallery/issues/228) in [bd8cdd3](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/bd8cdd302c55e285dc32a195e1d5f70b4312ac46).
- **feat(core):** Add `bulletsPosition` option, closes [#211](https://github.com/MurhafSousli/ngx-gallery/issues/211) in [263d297](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/263d297ccc27692fc9fd9702f775dcf2b753d5de).
- **feat(core):** Add `dotsSize` option, in [e2e58b6](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/e2e58b62e4eb3cbcf5dd6f17417830bfc956f7eb).
- **feat(core):** Add `counterPosition` option, closes in [ce7a8ad](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/ce7a8ad62816b3ba344a22e6e459cfdb06ad18ab).
- **feat(core):** Use `HttpClient` to load and cache images in `[lazyImage]` directive, in [15c3e88](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/15c3e88d89434eb4860eabf3959a08ce746298e7).
- **feat(core):** Replace icon loader with a new radial progress component to report image loading progress while keeping the svg loading icon as an option, in [a1028e8](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/a1028e84ae08d1e93c599f19aab32873d3dfea41).
- **feat(core):** Add default error template to `<gallery-image>` in case if loading failed and add `loadingError` option for custom error template, in [cd258f5](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/cd258f5481a01eb8065fbbc931505ca71c421cd5) and [9dbf6c4](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/9dbf6c43e78f91dbb8b2ccbe21f4575a255cd55a).
- **enhance(core):** Enhance gallery dots styles, in [de8d22b](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/de8d22bd6296d9c6a138152223ce6bffb91b6d63).
- **enhance(core):** Allow gallery image to use unsafe URLs, closes [#218](https://github.com/MurhafSousli/ngx-gallery/issues/218) in [da1ace1](https://github.com/MurhafSousli/ngx-gallery/commit/da1ace1bd18dca476110da06627afbaaf9ec6a21).
- **enhance(core):** Use `animationFrameScheduler` for smoother sliding animation, in [38b0aa6](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/38b0aa67fd1b73cd69e85daab3d2b1ec69da2696).
- **fix(core):** Fix vertical scroll when using the gallery on touch devices, closes [#161](https://github.com/MurhafSousli/ngx-gallery/issues/161) in [a239c29](https://github.com/MurhafSousli/ngx-gallery/pull/230/commits/a239c294248e1028a79a9f99a60462131d3729fc). **(kudos goes to [@harm-less](https://github.com/harm-less))**
- **refactor(core):** `[lazyImage]` directive => `(loaded)` event no longer emits on error.

- **feat(lightbox):** Set the focus back on the previously focused element when the lightbox is closed, in [266eddb](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/266eddb5afdfe51bec533a857431ac8dca395a0f).
- **feat(lightbox):** Add `role`, `ariaLabel`, `ariaLabelledBy` and `ariaDescribedBy` attributes to the lightbox config `LightboxConfig`, in [a11d20d](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/a11d20d5d4fa1fd2dbe46c88a7aa26aab58b3d04), [5b550e7](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/5b550e7ccb6dc6515e5b6284c69e0448bf181c91).
- **enhance(lightbox):** Import overlay default styles from `@angular/cdk/overlay`, in [54c5d88](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/54c5d88b6a3639f2066fdb7e1e7f74d307f823b6).
- **enhance(lightbox):** Improve lightbox styles, in [4a52161](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/4a521618c8969edeb903b182de0b5ff235efe8cd).
- **enhance(lightbox):** Update lightbox overlay animation, closes [#224](https://github.com/MurhafSousli/ngx-gallery/issues/224) in [bec077f](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/bec077f2335d47e65d6423a8eeda782dfb485f05).
- **refactor(lightbox):** Use `disposeOnNavigation` instead of `Location` service, in [2262164](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/22621648db640905482a58480a54b693b7894272).

## 3.3.1

- **fix(core):** Remove duplicate delete execution in the destroyer function, in [ae541ca](https://github.com/MurhafSousli/ngx-gallery/pull/214/commits/ae541cafb22e6d5c976950eca2c7779a39693b77)
- **fix(core):** Check galleryRef exists before deleting, in [a2b32e2](https://github.com/MurhafSousli/ngx-gallery/pull/214/commits/a2b32e23ccffb77c50c1067f1abea2977c2f1286)
- **fix(core):** Remove duplicate config set, in [834c001](https://github.com/MurhafSousli/ngx-gallery/pull/213/commits/834c001a6cccee9955e6e9504e0a0d4cb5691d57)
- **fix(core):** Remove unnecessary `PortalModule` import from `GalleryModule`, in [46ef735](https://github.com/MurhafSousli/ngx-gallery/pull/215/commits/46ef735cf43aa51ae026a1898b02e67b5909e520)
- **refactor(core):** Use `povidedIn: 'root'` for the `Gallery` service, in [86eeaa7](https://github.com/MurhafSousli/ngx-gallery/pull/215/commits/86eeaa71203341a324c06a3555489a5c82b8eee9)
- **fix(core, lightbox):** Fix peer dependencies, in [236e540](https://github.com/MurhafSousli/ngx-gallery/pull/215/commits/236e540ceaaa906aec65af0fdb99f866b5374c8f).

## 3.3.0

- **refactor(core):** Use `Map<string, GalleryRef>` for instances holder type instead of untyped object in [ac08077](https://github.com/MurhafSousli/ngx-gallery/commit/ac080772d7d05dd395811848cef174f31eaa622d).
- **refactor(core):** Rename gallery `state$` and `config$` to `state` and `config` in [8de515b](https://github.com/MurhafSousli/ngx-gallery/commit/8de515be8a254125c0a551f25bc6ad790b1e0703) and [1e4fd06](https://github.com/MurhafSousli/ngx-gallery/commit/1e4fd069dad4dd8b8edfac8a1a9dd97db72b770e).
- **enhance(core):** Improve instance destroyer, gallery delete its instance on component destroy in [65f3358](https://github.com/MurhafSousli/ngx-gallery/commit/65f3358c035907039bf5d8199f9c14ec0e13de15).

### Breaking Changes

- Gallery can now be destroyed using its instance `galleryRef.destroy()`.
- In `Gallery` service the function `destroy()` has been removed.

## 3.2.0

- **feature(core):** Do not require importing global styles, closes [#197](https://github.com/MurhafSousli/ngx-gallery/issues/197) in [ea041a5](https://github.com/MurhafSousli/ngx-gallery/commit/ea041a5930e1ecf184028d9444b2d7fa3faf80ae).
- **feature(core):** Set the video type attribute on videos items, closes [#199](https://github.com/MurhafSousli/ngx-gallery/issues/199) in [06b3601](https://github.com/MurhafSousli/ngx-gallery/commit/06b3601d382bd51cfdae3470921bc6a74aff0af9).
- **feature(lightbox):** Add a lightbox directive, closes [#200](https://github.com/MurhafSousli/ngx-gallery/issues/200) in [ad2255b](https://github.com/MurhafSousli/ngx-gallery/commit/ad2255be4abf44fb692bf2f90e29e5737cdf9ef1).
- **fix(gallery):** fix LazyImage error event, closes [#205](https://github.com/MurhafSousli/ngx-gallery/issues/205) in [db231aa](https://github.com/MurhafSousli/ngx-gallery/commit/db231aa9b1b95da2971ed35b500edd3ae6c2f8e0)

### Breaking changes

- No need to manually import the styles anymore, they are imported internally with the components.
- Adding a video item with multiple url sources
  
  **Before:**

```ts
galleryRef.addVideo({
  src: ['MP4_URL', 'OGG_URL'],
  thumb: '(OPTIONAL)VIDEO_THUMBNAIL_URL',
  poster: '(OPTIONAL)VIDEO_POSTER_URL'
});
```

**After:**

```ts
galleryRef.addVideo({
  src: [
    { url: 'MP4_URL', type: 'video/mp4' },
    { url: 'OGG_URL', type: 'video/ogg' }
  ],
  thumb: '(OPTIONAL)VIDEO_THUMBNAIL_URL',
  poster: '(OPTIONAL)VIDEO_POSTER_URL'
});
```

## 3.1.2

- **fix(Lightbox):** Check if location is defined before subscribing, closes [#189](https://github.com/MurhafSousli/ngx-gallery/issues/189) in [169b813](https://github.com/MurhafSousli/ngx-gallery/pull/190/commits/169b813bf1483e963b930666cbae902b83f24ef4).

## 3.1.1

- **refactor(core):** Convert `imageSize` attribute to an input + add it to gallery config, this makes it possible to use it in lightbox mode, closes [#183](https://github.com/MurhafSousli/ngx-gallery/issues/183) in [1fc70c4](https://github.com/MurhafSousli/ngx-gallery/pull/184/commits/1fc70c4e12c06199e2ae4395f2d182b771561acd).
- **refactor(core):** Make `contain` as the default value for `imageSize` option, in [c7b3d39](https://github.com/MurhafSousli/ngx-gallery/pull/185/commits/c7b3d39bfc257500c133cc954c8e72bc1d2ed672).

## 3.1.0

- **feat(core):** Add auto-play option, in [e7fc03f](https://github.com/MurhafSousli/ngx-gallery/pull/179/commits/e7fc03f3abc8c516634231ff42750806d54d22f0).
- **feat(core):** Add support for error handling, closes [#154](https://github.com/MurhafSousli/ngx-gallery/issues/154) in [12f6e5e](https://github.com/MurhafSousli/ngx-gallery/pull/177/commits/12f6e5eddd5a9c7607778b5af1f77ef782327930).
- **refactor(core):** Remove opacity transition from `gallery-item`, in [a5b227e](https://github.com/MurhafSousli/ngx-gallery/pull/179/commits/a5b227eb72f9531d1884c07329b65b3c95fc0228).
- **refactor(core):** Use `imageSize` as an attribute, in [96c5c07](https://github.com/MurhafSousli/ngx-gallery/pull/179/commits/96c5c075bf41d765b7c4667abcb374c7a6f80f1a).
- **refactor(core):** Rename `(player)` output to `(playingChange)`, in [e209493](https://github.com/MurhafSousli/ngx-gallery/pull/179/commits/e2094937c4303fcc00f223155f2ad3d9cee29e17).
- **enhance(core):** Use default cursor when thumbnails are disabled, in [3582e95](https://github.com/MurhafSousli/ngx-gallery/pull/179/commits/3582e95b0f3458c451f76580adda12f72220affb).
- **fix(core):** fix vertical sliding direction, in [cba5d59](https://github.com/MurhafSousli/ngx-gallery/pull/179/commits/cba5d59074fbd37be9ee22abbef2f181364d2267).
- **fix(core):** fix thumbClick Output, in [a730116](https://github.com/MurhafSousli/ngx-gallery/pull/179/commits/a730116e5d2154910d9a16c6a464e58b59f2e7dd).

## 3.1.0-beta.0

#### Gallery

- **feat(core):** Add `thumbMode` option on thumbnails' slider (free scroll thumbnails), closes [#135](https://github.com/MurhafSousli/ngx-gallery/issues/135) in [8c6c99d](https://github.com/MurhafSousli/ngx-gallery/pull/159/commits/8c6c99db551a3ee31f5ab203babba6464acc7b83).
- **feat(core):** Add slide show player option, closes [#152](https://github.com/MurhafSousli/ngx-gallery/issues/152) in [a331f46](https://github.com/MurhafSousli/ngx-gallery/pull/165/commits/a331f466ad4a2834a8de7f908d5e63fe1d3d5b41).
- **enhance(core):** Ability to Import gallery styles individually [#144](https://github.com/MurhafSousli/ngx-gallery/issues/144) in [ebb6667](https://github.com/MurhafSousli/ngx-gallery/pull/162/commits/ebb6667955299df1f41690a4f4b587a9196bb4bf).
- **enhance(core):** Run HammerJS gestures outside angular zone [6fabf6c](https://github.com/MurhafSousli/ngx-gallery/commit/6fabf6ca2d421ea1cc478c1f6cd9a3b432ddd0da).
- **enhance(core):** Put SCSS and CSS each in its own folder, close [#153](https://github.com/MurhafSousli/ngx-gallery/issues/153) in [9783fc3](https://github.com/MurhafSousli/ngx-gallery/pull/170/commits/9783fc3a614e2258c9363d316ea042fce8c86913).
- **enhance(core):** Check if loadingSvg is defined before embedding it, close [#150](https://github.com/MurhafSousli/ngx-gallery/issues/150) in [5286640](https://github.com/MurhafSousli/ngx-gallery/pull/171/commits/5286640de7ac3a5326d2d2c3d352a085b6dbc308).
- **fix(core):** fix wrong `(thumbClick)` emitter.
- **fix(core):** fix gallery slider width which is set to 0 at the beginning, closes [#151](https://github.com/MurhafSousli/ngx-gallery/issues/151) in [c26a286](https://github.com/MurhafSousli/ngx-gallery/commit/c26a286551aef4f0313295822f5a381a31479bcb).
- **refactor(core):** Set `loop` option to **true** by default.
- **refactor(core):** Remove `fluid` option from gallery config and use it as an attribute instead. fixed in [ecf3f88](https://github.com/MurhafSousli/ngx-gallery/pull/169/commits/ecf3f88f59ee0c2eaae68ebf1b93e580fc869a3a).

#### Lightbox

- feat(Lightbox): Close the lightbox when the location is changed, closes [#108](https://github.com/MurhafSousli/ngx-gallery/issues/108) in [1543374](https://github.com/MurhafSousli/ngx-gallery/commit/1543374c99b8d539f4e5df381ba9a7a60f3ccfa7).


### Breaking changes:

#### Gallery

- Fluid option is now used as an attribute, not as an input. 

**Before:**

```html
<gallery [fluid]="true"></gallery>
```

**After:**

```html
<gallery fluid></gallery>
```

- Scss and css styles are put each in its own folder

**Before:**

```scss
@import '~@ngx-gallery/core/styles/gallery';
```

**After:**

```scss
@import '~@ngx-gallery/core/styles/scss/gallery';
// or for css
@import '~@ngx-gallery/core/styles/css/gallery';
```

## 3.0.2

- refactor(Lightbox): fix the close button small size on iphone browser.
- refactor(Lightbox): use `<i>` tag instead of `<button>` tag for the close button.
- fix(core): Check if loadingIcon is defined in `<gallery-image>`, closes [#133](https://github.com/MurhafSousli/ngx-gallery/issues/133) and [#132](https://github.com/MurhafSousli/ngx-gallery/issues/132) in [24e6e26](https://github.com/MurhafSousli/ngx-gallery/commit/24e6e26fb2123b2db26e471f608efde86da553ff).

## 3.0.1

- feat(core): Allow using custom gallery item with custom template, closes [#125](https://github.com/MurhafSousli/ngx-gallery/issues/125) in [7e4c302](https://github.com/MurhafSousli/ngx-gallery/commit/7e4c3025276dd222f8592bc56694f5d278c8a6d6).

## 3.0.0

- fix(Lightbox): Close the lightbox when the active route is changed [#108](https://github.com/MurhafSousli/ngx-gallery/issues/108) in [d099abd](https://github.com/MurhafSousli/ngx-gallery/commit/d099abdf3b6a888c78dd3aa348c8b706ce43c8de).

## 3.0.0-beta.1

- refactor(core): add the `loop` input to gallery component, closes [#98](https://github.com/MurhafSousli/ngx-gallery/issues/98) in [727a4ca](https://github.com/MurhafSousli/ngx-gallery/commit/727a4ca7104db0ca62b03fa0fee66be0ee530fa0).

## 3.0.0-beta.0

### Features:

- Support Angular 6 and RxJS 6, closes [#91](https://github.com/MurhafSousli/ngx-gallery/issues/91).
- feat(core): Add helper functions to add different gallery items on `<gallery>` and `GalleryRef`.
- feat(core): Add `fluid` option to gallery for full width size.
- feat(core): Add `navIcon` option to gallery config to set a custom nav icon.
- feat(core): Add`loadingStrategy` option to gallery which accepts one of the following: 'preload', 'lazy' or 'default', closes [#87](https://github.com/MurhafSousli/ngx-gallery/issues/87).
- feat(core): Add `itemClick` output which emits when an item is clicked, closes [#106](https://github.com/MurhafSousli/ngx-gallery/pull/106/files).
- feat(core): Support custom template inside the default item templates, add `itemTemplate` and `thumbTemplate` to gallery options.
- feat(core): Multiple video sources support.
- feat(core): Pause Video and Youtube items when active item changes.
- feat(Gallerize): Add support to detect Gallery component.
- feat(Gallerize): Add support to detect DOM background images.

### Bug fixes:

- fix(core): Skip re-setting the config from `<gallery>` input in lightbox mode, closes [#104](https://github.com/MurhafSousli/ngx-gallery/issues/104).
- fix(core): Fix wrong thumbnail position when `[thumbPosition]` is changed.

### Improvements:

- refactor(core): Improve icon rendering, use svg element instead of background-image to render the nav icon in `<gallery-nav>`.
- refactor(core): Add `.g-active-item` on current item and `.g-active-thumb` on current thumbnail.
- refactor(core): Add `.g-image-loaded` class on `<gallery-image>` to indicates that the image has been loaded.
- refactor(core): Replace `loading` output with `loaded`, which emits the image path after it loads.
- refactor(core): Set an initial height of `500px`.
- refactor(core): Replace `ImageItem` `VideoItem` `YoutubeItem` and `IframeItem` constructor parameters with a single data parameter.
- refactor(core, Lightbox): Set `aria-label` on all buttons.
- refactor(Gallerize): Remove `forClass` input and replace it for `selector` input.
- refactor(Gallerize): Remove `CommonModule` as it is not needed.
- refactor(Styles): Add a prefix to all classes used in the plugin.
- refactor(Styles): Add a transition for animate the opacity on current item and thumbnail.


### Breaking changes:

#### Gallery

- Before, To Create an image item, we used to pass the src and the thumbnail separate parameters.

```ts
const item: GalleryItem = new ImageItem('IMAGE_SRC', 'THUMB_SRC');
```

- After, The parameters are replaced with a single data object.

```ts
const item: GalleryItem = new ImageItem({ src: 'IMAGE_SRC', thumb: 'THUMB_SRC' });
```

#### Gallerize

- Before, Limiting auto-detection to a specific class used to be done as in the following code:
 
```html
<div class="grid" gallerize forClass="my-img-class">
  <img class="my-img-class" src="{{item.src}}">
</div>
```

- After, Now `forClass` input has been replaced with `selector` input.

```html
<div class="grid" gallerize selector=".my-img-class">
  <img class="my-img-class" src="{{imgSource1}}">
  <div class="my-img-class" [style.background]="'url(' + imgSource2 + ')'">
</div>
```

## 2.1.1

- refactor(Lightbox Style): Clean up.
- fix(HammerJS): Don't throw an error if hammer is not defined, just fallback to default.
- feat(VideoItem): add a 3rd parameter to `VideoItem` to set custom poster.

```ts
const viewItem = new VideoItem(video.src, video.thumb, video.poster);
```

- refactor(core): rename `thumbSrc` to `thumb`.

### Breaking Changes

This won't effect the usage, but you might need to update 

`GalleryItem` data object has changed the name of the thumbnail source property from `thumbSrc` to `thumb`

This would only effect your app if you display the thumbnails list of your gallery items

Before

```html
<div class="grid">
  <div  class="grid-item"
        *ngFor="let item of galleryItems$ | async; let i = index"
        (click)="lightbox.open(i)">
    <img class="grid-image" [src]="item.data.thumbSrc">
  </div>
</div>
```

After

```html
<div class="grid">
  <div  class="grid-item"
        *ngFor="let item of galleryItems$ | async; let i = index"
        (click)="lightbox.open(i)">
    <img class="grid-image" [src]="item.data.thumb">
  </div>
</div>
```

## 2.0.4

- feat(GalleryConfig): add `loadingIcon` to GalleryConfig that accepts inline image.

## 2.0.3

- fix(Lightbox): Exit animation, closes [#73](https://github.com/MurhafSousli/ngx-gallery/issues/73).
- fix(Lightbox): close button is clicking behind, closes [#54](https://github.com/MurhafSousli/ngx-gallery/issues/54).
- refactor(Lightbox): Use the button tag instead of div for close button.

## 2.0.2

- enhancement(Gallerize): Use `MutationObserver` instead of `ngAfterContentChecked` to prevent infinite loop in default change detection strategy, closes [#70](https://github.com/MurhafSousli/ngx-gallery/issues/70).

## 2.0.1

- feat(GallerySlider): Rearrange slider on window resize, closes [#67](https://github.com/MurhafSousli/ngx-gallery/issues/67).

## 2.0.0

- fix(Swiping): Remove ngZone, closes [#64](https://github.com/MurhafSousli/ngx-gallery/issues/64).

## 2.0.0-beta.4

- feat(LightboxConfig): Adds fullscreen option to the lightbox, closes [#43](https://github.com/MurhafSousli/ngx-gallery/issues/43).

By default fullscreen is obtained on small screen (mobile) but now you can make it as default for all screens

```ts
GalleryModule.forRoot()
LightboxModule.forRoot({
  panelClass: 'fullscreen'
})
```

- feat(Lightbox): Ability to define lightbox config using `lightbox.open()` method

```ts
openLightbox() {
  this.lightbox.open(0, 'lightbox', {
    panelClass: 'fullscreen'
  });
}
```

## 2.0.0-beta.3

- Prevents native click event bubbling, closes [#57](https://github.com/MurhafSousli/ngx-gallery/issues/57)

## 2.0.0-beta

### Written from scratch

## 1.0.1

- fix double click on thumbnails and bullets, closes [#45](https://github.com/MurhafSousli/ng-gallery/issues/45).

## 1.0.0

**Fixes:**

- fix(GalleryNav): Hide navigation on panning.
- fix(GalleryPlayer): Wait until image is loaded before starting the timer.

 **Features:**
- feat(GalleryPlayer): Add progressbar color option.
- feat(GalleryPlayer): Add progressbar thickness option.
- feat(GalleryPlayer): Add position option `top` and `bottom`.
- feat(GalleryActions): Add gallery events
- feat(GalleryNav): Add `prevClass` and `nextClass` options to customize navigation icons
- feat(classNames) Add `className` option to container, thumbnails, bullets

**Performance Improvements:**

- refactor(GalleryThumbnail) improve performance

**Breaking Changes:**

- refactor(GalleryConfig): rename `config.thumbnails.space` to `config.thumbnails.margin`
- refactor(GalleryBullets): remove vertical positioning `right` and `left`

## 1.0.0-beta.8

- fix(keyboard listener in lightbox) closes [#24](https://github.com/MurhafSousli/ng-gallery/issues/24), [#33](https://github.com/MurhafSousli/ng-gallery/issues/33).
- refactor(Gallerize directive) Use MutationObserver instead of DOMSubtreeModified, closes [#26](https://github.com/MurhafSousli/ng-gallery/issues/26).
- fix(Universal support), closes [#9](https://github.com/MurhafSousli/ng-gallery/issues/9).
- fix Angular 5 warning, closes [#21](https://github.com/MurhafSousli/ng-gallery/issues/21).
- Improve gallery lightbox, closes [#20](https://github.com/MurhafSousli/ng-gallery/issues/20).
- Improve gallery lightbox slide animation, closes [#8](https://github.com/MurhafSousli/ng-gallery/issues/8).
- Use Angular CDK for the gallery lightbox.
- refactor(GalleryConfig)
- Remove image transition animation option because it was not implemented properly.

## 0.7.1

- General refactor
- fix(GalleryDirective) apply gallerize only once when content changes
- decode gallery nav icons and close button from base64 to decrease the size

## 0.7.0

- feat(LazyLoad) emit only last selected image.
- fix(GalleryImage) fade animation is working properly with image load.
- refactor(GalleryConfig)

## 0.6.3

- fix(GalleryModal) close button is hidden on mobile, closes [#9](https://github.com/MurhafSousli/ng-gallery/issues/9)
- fix umd bundle for systemjs, closes [#10](https://github.com/MurhafSousli/ng-gallery/issues/10)

## 0.6.2

- fix(gestures) remove navigation element on mobile which was blocking gestures events
- fix(gestures) enable/disable gestures using `config.gestures`
- refactor(config) interfaces

## 0.6.0 beta

- Add popup animation for gallery modal
- Remove incorrect slide animation
- Make gestures optional, closes [#2](https://github.com/MurhafSousli/ng-gallery/issues/2)
- Remove thumbnail vertical position (right and left) positions, closes [#3](https://github.com/MurhafSousli/ng-gallery/issues/3)

## 0.5.2 beta

- (feat) gestures support
- (refactor) gallery config 

## 0.5.0 beta

- Initial release 


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2016-2025 Murhaf Sousli

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.


================================================
FILE: README.md
================================================
<p align="center">
  <img width="150px" src="https://user-images.githubusercontent.com/8130692/36171173-ad0da54c-1112-11e8-85bf-843c5f70efdc.png" style="max-width:100%;">
</p>
<h1 align="center">Angular Gallery</h1>

<p align="center">Simplifies the process of creating beautiful image galleries for the web and mobile devices.</p>


[![npm](https://img.shields.io/badge/demo-online-ed1c46.svg)](https://ngx-gallery.netlify.app)
[![npm](https://img.shields.io/badge/stackblitz-online-orange.svg)](https://stackblitz.com/edit/ngx-gallery)
[![npm](https://img.shields.io/npm/v/ng-gallery.svg?maxAge=2592000?style=plastic)](https://www.npmjs.com/package/ng-gallery)
[![tests](https://github.com/MurhafSousli/ngx-gallery/workflows/tests/badge.svg)](https://github.com/MurhafSousli/ngx-gallery/actions?query=workflow%3Atests)
[![codecov](https://codecov.io/gh/MurhafSousli/ngx-gallery/graph/badge.svg?token=krc4nTzgGR)](https://codecov.io/gh/MurhafSousli/ngx-gallery)
[![npm](https://img.shields.io/npm/dt/ng-gallery.svg?maxAge=2592000?style=plastic)](https://www.npmjs.com/package/ng-gallery)
[![npm](https://img.shields.io/npm/l/express.svg?maxAge=2592000)](/LICENSE)


___

### Explore ngx-gallery Documentation

- For **v11:** use the [wiki page](https://github.com/MurhafSousli/ngx-gallery/wiki) 📚.

- For **v12:** use the [storybook documentation](https://ngx-gallery-next.netlify.app/)

We value your feedback and appreciate your support in testing this beta release!

___


## Support

[![npm](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/bePatron?u=5594898)

## Issues

If you identify any errors in this module, or have an idea for an improvement, please open an [issue](https://github.com/MurhafSousli/ngx-gallery/issues).

## Author

**[Murhaf Sousli](http://murhafsousli.com)**

- [github/murhafsousli](https://github.com/MurhafSousli)
- [twitter/murhafsousli](https://twitter.com/MurhafSousli)


================================================
FILE: angular.json
================================================
{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "ngx-gallery-demo": {
      "projectType": "application",
      "schematics": {
        "@schematics/angular:component": {
          "style": "scss"
        }
      },
      "root": "projects/ng-gallery-demo",
      "sourceRoot": "projects/ng-gallery-demo/src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:application",
          "options": {
            "allowedCommonJsDependencies": [
            ],
            "outputPath": "dist/ngx-gallery-demo",
            "index": "projects/ng-gallery-demo/src/index.html",
            "browser": "projects/ng-gallery-demo/src/main.ts",
            "polyfills": [
              "zone.js"
            ],
            "tsConfig": "projects/ng-gallery-demo/tsconfig.app.json",
            "inlineStyleLanguage": "scss",
            "assets": [
              {
                "glob": "**/*",
                "input": "projects/ng-gallery-demo/public"
              }
            ],
            "styles": [
              "projects/ng-gallery-demo/src/styles.scss"
            ],
            "scripts": [],
            "server": "projects/ng-gallery-demo/src/main.server.ts",
            "prerender": true,
            "ssr": {
              "entry": "projects/ng-gallery-demo/src/server.ts"
            }
          },
          "configurations": {
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "3mb"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "15kb",
                  "maximumError": "15kb"
                }
              ],
              "outputHashing": "all"
            },
            "development": {
              "optimization": false,
              "extractLicenses": false,
              "sourceMap": true
            }
          },
          "defaultConfiguration": "production"
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "configurations": {
            "production": {
              "buildTarget": "ngx-gallery-demo:build:production"
            },
            "development": {
              "buildTarget": "ngx-gallery-demo:build:development"
            }
          },
          "defaultConfiguration": "development"
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "buildTarget": "ngx-gallery-demo:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "polyfills": [
              "zone.js",
              "zone.js/testing"
            ],
            "tsConfig": "projects/ng-gallery-demo/tsconfig.spec.json",
            "inlineStyleLanguage": "scss",
            "assets": [
              {
                "glob": "**/*",
                "input": "projects/ng-gallery-demo/public"
              }
            ],
            "styles": [
              "projects/ng-gallery-demo/src/styles.scss"
            ],
            "scripts": []
          }
        }
      }
    },
    "ngx-gallery": {
      "projectType": "library",
      "root": "projects/ng-gallery",
      "sourceRoot": "projects/ng-gallery/src",
      "prefix": "lib",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:ng-packagr",
          "options": {
            "project": "projects/ng-gallery/ng-package.json"
          },
          "configurations": {
            "production": {
              "tsConfig": "projects/ng-gallery/tsconfig.lib.prod.json"
            },
            "development": {
              "tsConfig": "projects/ng-gallery/tsconfig.lib.json"
            }
          },
          "defaultConfiguration": "production"
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "tsConfig": "projects/ng-gallery/tsconfig.spec.json",
            "polyfills": [
              "zone.js",
              "zone.js/testing"
            ],
            "karmaConfig": "projects/ng-gallery/karma.conf.js"
          }
        },
        "lint": {
          "builder": "@angular-eslint/builder:lint",
          "options": {
            "lintFilePatterns": [
              "projects/ng-gallery/**/*.ts",
              "projects/ng-gallery/**/*.html"
            ]
          }
        }
      }
    }
  },
  "cli": {
    "schematicCollections": [
      "@angular-eslint/schematics"
    ]
  }
}


================================================
FILE: package.json
================================================
{
  "name": "ng-gallery-project",
  "version": "0.0.0",
  "scripts": {
    "start-demo": "ng serve ngx-gallery-demo --host 0.0.0.0",
    "build-demo": "ng build ngx-gallery-demo --configuration production",
    "build-lib": "ng build ngx-gallery --configuration production",
    "lint-lib": "ng lint ngx-gallery",
    "test-lib": "ng test ngx-gallery",
    "test-lib-headless": "ng test ngx-gallery --watch=false --no-progress --browsers=ChromeHeadless --code-coverage",
    "publish-lib": "npm publish ./dist/ng-gallery",
    "storybook": "ng run ngx-gallery:storybook",
    "build-storybook": "ng run ngx-gallery:build-storybook",
    "init-msw": "msw init src/",
    "chromatic": "npx chromatic --project-token=chpt_4359317e034d873",
    "serve:ssr:ng-gallery-demo": "node dist/ngx-gallery-demo/server/server.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/MurhafSousli/ngx-gallery.git"
  },
  "dependencies": {
    "@angular/animations": "^19.1.5",
    "@angular/cdk": "^19.1.3",
    "@angular/common": "^19.1.5",
    "@angular/compiler": "^19.1.5",
    "@angular/core": "^19.1.5",
    "@angular/forms": "^19.1.5",
    "@angular/material": "^19.1.3",
    "@angular/platform-browser": "^19.1.5",
    "@angular/platform-browser-dynamic": "^19.1.5",
    "@angular/platform-server": "^19.1.5",
    "@angular/router": "^19.1.5",
    "@angular/ssr": "^19.0.6",
    "@fortawesome/angular-fontawesome": "^1.0.0",
    "@fortawesome/fontawesome-svg-core": "^6.7.2",
    "@fortawesome/free-brands-svg-icons": "^6.7.2",
    "@fortawesome/free-solid-svg-icons": "^6.7.2",
    "hammerjs": "^2.0.8",
    "highlight.js": "^11.11.1",
    "ngx-highlightjs": "^14.0.0",
    "ngx-progressbar": "^14.0.0",
    "ngx-scrollbar": "^18.0.0",
    "rxjs": "~7.8.1",
    "tslib": "^2.8.1",
    "zone.js": "^0.15.0"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^19.1.6",
    "@angular-eslint/builder": "19.1.0",
    "@angular-eslint/eslint-plugin": "19.1.0",
    "@angular-eslint/eslint-plugin-template": "19.1.0",
    "@angular-eslint/schematics": "19.1.0",
    "@angular-eslint/template-parser": "19.1.0",
    "@angular/cli": "^19.1.6",
    "@angular/compiler-cli": "^19.1.5",
    "@types/express": "^4.17.17",
    "@types/jasmine": "~5.1.5",
    "@types/node": "^22.10.3",
    "@typescript-eslint/eslint-plugin": "^8.24.0",
    "@typescript-eslint/parser": "^8.24.0",
    "@typescript-eslint/types": "^8.24.0",
    "@typescript-eslint/utils": "^8.24.0",
    "eslint": "^9.17.0",
    "express": "^4.18.2",
    "jasmine-core": "^5.5.0",
    "karma": "~6.4.4",
    "karma-chrome-launcher": "~3.2.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.1.0",
    "ng-packagr": "^19.1.2",
    "typescript": "~5.5.4"
  }
}


================================================
FILE: projects/ng-gallery/.eslintrc.json
================================================
{
  "root": true,
  "ignorePatterns": [
    "projects/**/*"
  ],
  "overrides": [
    {
      "files": [
        "*.ts"
      ],
      "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:@angular-eslint/recommended",
        "plugin:@angular-eslint/template/process-inline-templates"
      ],
      "rules": {
        "@angular-eslint/directive-selector": [
          "error",
          {
            "type": "attribute",
            "prefix": "",
            "style": "camelCase"
          }
        ],
        "@angular-eslint/component-selector": [
          "error",
          {
            "type": "element",
            "prefix": "",
            "style": "kebab-case"
          }
        ],
        "@angular-eslint/component-class-suffix": 0,
        "@angular-eslint/directive-class-suffix": 0,
        "@angular-eslint/no-input-rename": 0,
        "@angular-eslint/no-output-rename": 0,
        "@angular-eslint/no-output-native": 0,
        "@angular-eslint/no-host-metadata-property": 0
      }
    },
    {
      "files": [
        "*.html"
      ],
      "extends": [
        "plugin:@angular-eslint/template/recommended",
        "plugin:@angular-eslint/template/accessibility"
      ],
      "rules": {
        "@angular-eslint/template/elements-content": 0
      }
    }
  ]
}


================================================
FILE: projects/ng-gallery/.storybook/colors.mdx
================================================
{/* Colors.mdx */}

import { Meta, ColorPalette, ColorItem } from '@storybook/blocks';

<Meta title="Colors" />

<ColorPalette>
  <ColorItem
    title="theme.color.greyscale"
    subtitle="Some of the greys"
    colors={{ White: '#FFFFFF', Alabaster: '#F8F8F8', Concrete: '#F3F3F3' }}
  />
  <ColorItem
    title="theme.color.primary"
    subtitle="Coral"
    colors={{ WildWatermelon: '#FF4785' }}
  />
  <ColorItem
    title="theme.color.secondary"
    subtitle="Ocean"
    colors={{ DodgerBlue: '#1e5dfd' }}
  />
  <ColorItem
    title="theme.color.positive"
    subtitle="Green"
    colors={{
      Apple: 'rgba(102,191,60,1)',
      Apple80: 'rgba(102,191,60,.8)',
      Apple60: 'rgba(102,191,60,.6)',
      Apple30: 'rgba(102,191,60,.3)',
    }}
  />
</ColorPalette>


================================================
FILE: projects/ng-gallery/.storybook/main.ts
================================================
import type { StorybookConfig } from "@storybook/angular";

const config: StorybookConfig = {
  stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-interactions"
  ],
  framework: {
    name: "@storybook/angular",
    options: {},
  },
  docs: {}
};
export default config;


================================================
FILE: projects/ng-gallery/.storybook/manager.js
================================================
import { addons } from '@storybook/manager-api';
import theme from './theme';

addons.setConfig({
  theme: theme,
});


================================================
FILE: projects/ng-gallery/.storybook/preview.ts
================================================
import type { Preview } from "@storybook/angular";
import { setCompodocJson } from "@storybook/addon-docs/angular";

import docJson from "../documentation.json";
setCompodocJson(docJson);

const preview: Preview = {
  parameters: {
    layout: 'fullscreen',
    actions: { argTypesRegex: "^on[A-Z].*" },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      }
    }
  },
};

export default preview;


================================================
FILE: projects/ng-gallery/.storybook/theme.js
================================================
import { create } from '@storybook/theming/create';

export default create({
  base: 'dark',
  // Typography
  fontBase: '"Open Sans", sans-serif',
  fontCode: 'monospace',

  brandTitle: 'Angular Gallery',
  brandUrl: 'https://github.com/MurhafSousli/ngx-gallery',
  brandImage: 'https://github.com/MurhafSousli/ngx-gallery/assets/8130692/f9a4a981-7a61-4f3d-9e00-06ec3b2efd1c',
  brandTarget: '_self',

  //
  colorPrimary: '#3a95ff',
  colorSecondary: '#c2c6ce',

  // UI
  // appBg: '#ffffff',
  appContentBg: '#797979',
  // appBorderColor: '#585C6D',
  // appBorderRadius: 4,

  // Text colors
  // textColor: '#10162F',
  // textInverseColor: '#ffffff',

  // Toolbar default and active colors
  // barTextColor: '#9E9E9E',
  // barSelectedColor: '#585C6D',
  // barBg: '#ffffff',

  // Form colors
  // inputBg: '#ffffff',
  // inputBorder: '#10162F',
  // inputTextColor: '#10162F',
  // inputBorderRadius: 2,
});


================================================
FILE: projects/ng-gallery/.storybook/tsconfig.json
================================================
{
  "extends": "../tsconfig.lib.json",
  "compilerOptions": {
    "types": ["node"],
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true
  },
  "exclude": ["../src/test.ts", "../src/**/*.spec.ts"],
  "include": ["../src/**/*", "./preview.ts"],
  "files": ["./typings.d.ts"]
}


================================================
FILE: projects/ng-gallery/.storybook/typings.d.ts
================================================
declare module '*.md' {
  const content: string;
  export default content;
}


================================================
FILE: projects/ng-gallery/README.md
================================================
<p align="center">
  <img width="150px" src="https://user-images.githubusercontent.com/8130692/36171173-ad0da54c-1112-11e8-85bf-843c5f70efdc.png" style="max-width:100%;">
</p>
<h1 align="center">Angular Gallery</h1>

<p align="center">Simplifies the process of creating beautiful image galleries for the web and mobile devices.</p>


[![npm](https://img.shields.io/badge/demo-online-ed1c46.svg)](https://ngx-gallery.netlify.app)
[![npm](https://img.shields.io/badge/stackblitz-online-orange.svg)](https://stackblitz.com/edit/ngx-gallery)
[![npm](https://img.shields.io/npm/v/ng-gallery.svg?maxAge=2592000?style=plastic)](https://www.npmjs.com/package/ng-gallery)
[![tests](https://github.com/MurhafSousli/ngx-gallery/workflows/tests/badge.svg)](https://github.com/MurhafSousli/ngx-gallery/actions?query=workflow%3Atests)
[![npm](https://img.shields.io/npm/dt/ng-gallery.svg?maxAge=2592000?style=plastic)](https://www.npmjs.com/package/ng-gallery)
[![npm](https://img.shields.io/npm/l/express.svg?maxAge=2592000)](/LICENSE)


___

### Explore ngx-gallery Documentation

- For **v11:** use the [wiki page](https://github.com/MurhafSousli/ngx-gallery/wiki) 📚.

- For **v12:** use the [storybook documentation](https://ngx-gallery-next.netlify.app/)

We value your feedback and appreciate your support in testing this beta release!

___


## Support

[![npm](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/bePatron?u=5594898)

## Issues

If you identify any errors in this module, or have an idea for an improvement, please open an [issue](https://github.com/MurhafSousli/ngx-gallery/issues).

## Author

**[Murhaf Sousli](http://murhafsousli.com)**

- [github/murhafsousli](https://github.com/MurhafSousli)
- [twitter/murhafsousli](https://twitter.com/MurhafSousli)


================================================
FILE: projects/ng-gallery/karma.conf.js
================================================
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html

module.exports = function (config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage'),
      require('@angular-devkit/build-angular/plugins/karma')
    ],
    client: {
      jasmine: {
        // you can add configuration options for Jasmine here
        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
        // for example, you can disable the random execution with `random: false`
        // or set a specific seed with `seed: 4321`
      },
      clearContext: false // leave Jasmine Spec Runner output visible in browser
    },
    jasmineHtmlReporter: {
      suppressAll: true // removes the duplicated traces
    },
    coverageReporter: {
      dir: require('path').join(__dirname, '../../coverage/ng-gallery'),
      subdir: '.',
      reporters: [
        { type: 'html' },
        { type: 'text-summary' },
        { type: 'cobertura' },
        { type: 'lcov' }
      ]
    },
    reporters: ['progress', 'kjhtml'],
    browsers: ["MyChromeWithoutSearchSelect"],
    customLaunchers: {
      MyChromeWithoutSearchSelect: {
        base: "Chrome",
        flags: ["-disable-search-engine-choice-screen"],
      },
    },
    restartOnFileChange: true
  });
};


================================================
FILE: projects/ng-gallery/lightbox/ng-package.json
================================================
{}


================================================
FILE: projects/ng-gallery/lightbox/src/gallerize.directive.ts
================================================
import {
  Directive,
  Input,
  OnInit,
  OnDestroy,
  Inject,
  Optional,
  Self,
  Host,
  NgZone,
  ElementRef,
} from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { GalleryRef, GalleryComponent, GalleryItemData } from 'ng-gallery';
import { Subject, Subscription, from, map, switchMap, finalize, debounceTime, EMPTY } from 'rxjs';

import { Lightbox } from './lightbox.service';

/**
 * This directive has 2 modes:
 * 1 - If host element is a HTMLElement, it detects the images and hooks their clicks to lightbox
 * 2 - If host element is a GalleryComponent, it hooks the images click to the lightbox
 */

const enum GallerizeMode {
  Detector = 'detector',
  Gallery = 'gallery'
}

@Directive({
  selector: '[gallerize]',
  standalone: true
})
export class GallerizeDirective implements OnInit, OnDestroy {

  /** Default gallery id */
  private _galleryId: string = 'lightbox';

  /** Gallerize mode */
  private readonly _mode: GallerizeMode;

  /** If host element is a HTMLElement, will use the following variables: */

  /** Stream that emits to fire the detection stream the image elements has changed */
  private _observer$: MutationObserver;

  /** Stream that emits when image is discover */
  private _detector$: Subject<void>;

  /** If host element is a GalleryComponent, will use the following variables: */

  /** Gallery events (if used on a gallery component) */
  private _itemClick$: Subscription;
  private _itemChange$: Subscription;

  // ======================================================

  /** If set, it will become the gallery id */
  @Input() gallerize: string;

  /** The selector used to query images elements */
  @Input() selector: string = 'img';

  constructor(private _zone: NgZone,
              private _el: ElementRef,
              // private _gallery: Gallery,
              private _lightbox: Lightbox,
              @Inject(DOCUMENT) private _document: Document,
              @Host() @Self() @Optional() private _galleryCmp: GalleryComponent) {

    // Set gallerize mode
    this._mode = _galleryCmp ? GallerizeMode.Gallery : GallerizeMode.Detector;
  }

  ngOnInit(): void {
    this._zone.runOutsideAngular(() => {
      this._galleryId = this.gallerize || this._galleryId;
      // const ref: GalleryRef = this._gallery.ref(this._galleryId);

      // switch (this._mode) {
      //   case GallerizeMode.Detector:
      //     this.detectorMode(ref);
      //     break;
      //   case GallerizeMode.Gallery:
      //     this.galleryMode(ref);
      // }
    });
  }

  ngOnDestroy(): void {
    switch (this._mode) {
      case GallerizeMode.Detector:
        this._detector$.complete();
        this._observer$.disconnect();
        break;
      case GallerizeMode.Gallery:
        this._itemClick$.unsubscribe();
        this._itemChange$.unsubscribe();
    }
  }

  /** Gallery mode: means `gallerize` directive is used on `<gallery>` component
   * Adds a click event to each gallery item so it opens in lightbox */
  private galleryMode(galleryRef: GalleryRef): void {
    // Clone its items to the new gallery instance
    this._itemClick$ = this._galleryCmp.galleryRef.itemClick.subscribe((i: number) => this._lightbox.open(i, this._galleryId));
    this._itemChange$ = this._galleryCmp.galleryRef.itemsChanged.subscribe(() => galleryRef.load(this._galleryCmp.galleryRef.items()));
  }

  /** Detector mode: means `gallerize` directive is used on a normal HTMLElement
   *  Detects images and adds a click event to each image, so it opens in the lightbox */
  private detectorMode(galleryRef: GalleryRef): void {
    this._detector$ = new Subject();
    // Query image elements
    this._detector$.pipe(
      debounceTime(300),
      switchMap(() => {

        /** get all img elements from content */
        const imageElements = this._el.nativeElement.querySelectorAll(this.selector);

        if (imageElements && imageElements.length) {

          const images: GalleryItemData[] = [];

          return from(imageElements).pipe(
            map((el: HTMLElement, i: number) => {
              // Add click event to the image
              el.style.cursor = 'pointer';
              el.addEventListener('click', () => {
                this._zone.run(() => this._lightbox.open(i, this._galleryId));
              });

              if (el instanceof HTMLImageElement) {
                // If element is type of img use the src property
                return {
                  src: el.getAttribute('imageSrc') || el.src,
                  thumb: el.getAttribute('thumbSrc') || el.src
                };
              } else {
                // Otherwise, use element background-image url
                const elStyle = this._document.defaultView.getComputedStyle(el, null);
                const background = elStyle.backgroundImage.slice(4, -1).replace(/"/g, '');
                return {
                  src: el.getAttribute('imageSrc') || background,
                  thumb: el.getAttribute('thumbSrc') || background
                };
              }
            }),
            // tap((data: any) => images.push(new ImageItem(data))),
            finalize(() => galleryRef.load(images))
          );
        } else {
          return EMPTY;
        }
      })
    ).subscribe();

    // Observe content changes
    this._observer$ = new MutationObserver(() => this._detector$.next());
    this._observer$.observe(this._el.nativeElement, { childList: true, subtree: true });
  }
}


================================================
FILE: projects/ng-gallery/lightbox/src/lightbox.animation.ts
================================================
import { animate, state, style, transition, trigger } from '@angular/animations';

export const lightboxAnimation = trigger('lightbox', [
  state('void, exit', style({ opacity: 0, transform: 'scale(0.7)' })),
  state('enter', style({ transform: 'none' })),
  transition('* => enter', animate('{{startAnimationTime}}ms cubic-bezier(0, 0, 0.2, 1)',
    style({ transform: 'none', opacity: 1 }))),
  transition('* => void, * => exit',
    animate('{{exitAnimationTime}}ms cubic-bezier(0.4, 0.0, 0.2, 1)', style({ opacity: 0 }))),
]);


================================================
FILE: projects/ng-gallery/lightbox/src/lightbox.component.scss
================================================
@mixin fullscreen() {
  width: 100%;

  gallery {
    max-width: unset;
    max-height: unset;
    //position: fixed;
    //top: 0;
    //left: 0;
    //bottom: 0;
    //right: 0;
    height: 100%;
    width: 100%;
    border-radius: 0;
  }
}

:host {
  display: contents;

  ::ng-deep {
    gallery {
      position: relative;
      display: block;
      width: 1100px;
      //height: 800px;
      max-width: 94vw;
      max-height: 90vh;
      margin: 0;
    }
  }
}

dialog {
  border: none;
  padding: 0;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 11px 15px -7px rgba(0, 0, 0, .2), 0 24px 38px 3px rgba(0, 0, 0, .14), 0 9px 46px 8px rgba(0, 0, 0, .12);
  max-width: 80%;
  animation: vanish 400ms cubic-bezier(0, 0, 0.2, 1);

  &::backdrop {
    animation: backdropVanish 400ms cubic-bezier(0, 0, 0.2, 1);
    background-image: linear-gradient(
        45deg,
        magenta,
        rebeccapurple,
        dodgerblue,
        green
    );
    opacity: 0.75;
  }

  &[open] {
    opacity: 1;
    animation: appear 400ms cubic-bezier(0, 0, 0.2, 1);

    &::backdrop {
      animation: backdropAppear 400ms cubic-bezier(0, 0, 0.2, 1);
    }
  }
}

@keyframes appear {
  from {
    opacity: 0;
    scale: 0.7;
  }
  to {
    opacity: 1;
    scale: 1;
  }
}

@keyframes vanish {
  from {
    display: block;
    opacity: 1;
    scale: 1;
  }
  to {
    display: none;
    opacity: 0;
    scale: 0.7;
  }
}

@keyframes backdropAppear {
  from {
    opacity: 0;
  }
  to {
    opacity: 0.75;
  }
}

@keyframes backdropVanish {
  from {
    display: block;
    opacity: 0.75;
  }
  to {
    display: none;
    opacity: 0;
  }
}

.g-backdrop {
  background-color: rgba(0, 0, 0, .32);
}

.fullscreen {
  @include fullscreen();
}

.g-overlay {
  margin: auto;

  @media only screen and (max-width: 480px) {
    @include fullscreen();
  }
}

.g-btn-close {
  position: absolute;
  right: 0.9em;
  top: 0.9em;
  z-index: 60;
  //cursor: pointer;
  //width: 20px;
  //height: 20px;
  //@media only screen and (max-width: 480px) {
  //  right: 0.7em;
  //  top: 0.7em;
  //}

  //svg {
  //  width: 100%;
  //  height: 100%;
  //  opacity: 0.6;
  //  transition: opacity linear 150ms;
  //  filter: drop-shadow(0px 0px 2px rgba(0, 0, 0, 0.8));
  //
  //  &:hover {
  //    opacity: 1;
  //  }
  //}
}


================================================
FILE: projects/ng-gallery/lightbox/src/lightbox.component.ts
================================================
import { Component, viewChild, contentChild, Signal, ElementRef, ChangeDetectionStrategy } from '@angular/core';
import { GalleryComponent } from 'ng-gallery';

@Component({
  selector: 'lightbox',
  template: `
    <dialog #dialogElement>
      <button class="g-btn-close" aria-label="Close" (click)="close()">Close</button>

      <ng-content select="gallery"/>
    </dialog>
  `,
  styleUrl: './lightbox.component.scss',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class LightboxComponent {

  dialog: Signal<ElementRef<HTMLDialogElement>> = viewChild('dialogElement');

  gallery: Signal<GalleryComponent> = contentChild(GalleryComponent);

  showModal(i: number): void {
    this.gallery().set(i, 'auto');
    this.dialog().nativeElement.showModal();
  }

  close(): void {
    this.dialog().nativeElement.close();
  }
}


================================================
FILE: projects/ng-gallery/lightbox/src/lightbox.default.ts
================================================
import { LightboxConfig } from './lightbox.model';

export const defaultConfig: LightboxConfig = {
  backdropClass: 'g-backdrop',
  panelClass: 'g-overlay',
  hasBackdrop: true,
  keyboardShortcuts: true,
  role: 'lightbox',
  startAnimationTime: 150,
  exitAnimationTime: 75,
  closeIcon: `<?xml version="1.0" encoding="UTF-8"?>
<svg width="512px" height="512px" enable-background="new 0 0 47.971 47.971" version="1.1" viewBox="0 0 47.971 47.971" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
	<path d="M28.228,23.986L47.092,5.122c1.172-1.171,1.172-3.071,0-4.242c-1.172-1.172-3.07-1.172-4.242,0L23.986,19.744L5.121,0.88   c-1.172-1.172-3.07-1.172-4.242,0c-1.172,1.171-1.172,3.071,0,4.242l18.865,18.864L0.879,42.85c-1.172,1.171-1.172,3.071,0,4.242   C1.465,47.677,2.233,47.97,3,47.97s1.535-0.293,2.121-0.879l18.865-18.864L42.85,47.091c0.586,0.586,1.354,0.879,2.121,0.879   s1.535-0.293,2.121-0.879c1.172-1.171,1.172-3.071,0-4.242L28.228,23.986z" fill="#fff"/>
</svg>
`
};


================================================
FILE: projects/ng-gallery/lightbox/src/lightbox.directive.ts
================================================
// import { Directive, Input, ElementRef, OnInit, OnDestroy, Renderer2 } from '@angular/core';
// import { fromEvent, tap, SubscriptionLike, Subscription } from 'rxjs';
// import { Lightbox } from './lightbox.service';
//
// @Directive({
//   selector: '[lightbox]'
// })
// export class LightboxDirective implements OnInit, OnDestroy {
//
//   clickEvent: SubscriptionLike = Subscription.EMPTY;
//
//   @Input('lightbox') index = 0;
//   @Input('gallery') id = 'root';
//
//   constructor(private _lightbox: Lightbox, private _el: ElementRef, private _renderer: Renderer2) {
//   }
//
//   ngOnInit() {
//     // this._renderer.setStyle(this._el.nativeElement, 'cursor', 'pointer');
//     // this.clickEvent = fromEvent(this._el.nativeElement, 'click').pipe(
//     //   tap(() => this._lightbox.open(this.index, this.id))
//     // ).subscribe();
//   }
//
//   ngOnDestroy() {
//     this.clickEvent.unsubscribe();
//   }
// }


================================================
FILE: projects/ng-gallery/lightbox/src/lightbox.model.ts
================================================
import { InjectionToken, Provider } from '@angular/core';
import { defaultConfig } from './lightbox.default';

export const LIGHTBOX_CONFIG: InjectionToken<LightboxConfig> = new InjectionToken<LightboxConfig>('LIGHTBOX_CONFIG', {
  providedIn: 'root',
  factory: () => defaultConfig
});

export interface LightboxConfig {
  backdropClass?: string | string[];
  panelClass?: string | string[];
  hasBackdrop?: boolean;
  keyboardShortcuts?: boolean;
  closeIcon?: string;
  role?: string;
  ariaLabelledBy?: string;
  ariaLabel?: string;
  ariaDescribedBy?: string;
  startAnimationTime?: number;
  exitAnimationTime?: number;
}

export function provideLightboxOptions(options: LightboxConfig): Provider {
  return {
    provide: LIGHTBOX_CONFIG,
    useValue: { ...defaultConfig, ...options }
  }
}


================================================
FILE: projects/ng-gallery/lightbox/src/lightbox.module.ts
================================================
import { NgModule } from '@angular/core';
import { GalleryModule } from 'ng-gallery';
// import { LightboxDirective } from './lightbox.directive';
import { GallerizeDirective } from './gallerize.directive';

@NgModule({
  imports: [
    GalleryModule,
    // LightboxDirective,
    GallerizeDirective
  ],
  exports: [
    GalleryModule,
    // LightboxDirective,
    GallerizeDirective
  ]
})
export class LightboxModule {
}


================================================
FILE: projects/ng-gallery/lightbox/src/lightbox.service.ts
================================================
import { ComponentRef, inject, Injectable } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { ComponentPortal } from '@angular/cdk/portal';
import { Overlay, OverlayRef, OverlayConfig } from '@angular/cdk/overlay';
import { LEFT_ARROW, RIGHT_ARROW, ESCAPE } from '@angular/cdk/keycodes';
// import { Gallery } from 'ng-gallery';
import { Subject } from 'rxjs';

import { LightboxConfig, LIGHTBOX_CONFIG } from './lightbox.model';
import { LightboxComponent } from './lightbox.component';

@Injectable({
  providedIn: 'root'
})
export class Lightbox {

  /** Gallery overlay ref */
  private _overlayRef: OverlayRef;

  // private _gallery: Gallery = inject(Gallery);

  private _overlay: Overlay = inject(Overlay);

  private _sanitizer: DomSanitizer = inject(DomSanitizer);

  /** Global config */
  private _config: LightboxConfig = inject(LIGHTBOX_CONFIG);

  /** Stream that emits when lightbox is opened */
  opened: Subject<string> = new Subject<string>();

  /** Stream that emits when lightbox is closed */
  closed: Subject<string> = new Subject<string>();

  /**
   * Set Lightbox Config
   * @param config - LightboxConfig
   */
  setConfig(config: LightboxConfig) {
    this._config = { ...this._config, ...config };
  }

  /**
   * Open Lightbox Overlay
   * @param i - Current Index
   * @param id - Gallery ID
   * @param config - Lightbox Config
   */
  open(i = 0, id = 'lightbox', config?: LightboxConfig) {

    const _config: LightboxConfig = config ? { ...this._config, ...config } : this._config;

    const overlayConfig: OverlayConfig = {
      backdropClass: _config.backdropClass,
      panelClass: _config.panelClass,
      hasBackdrop: _config.hasBackdrop,
      positionStrategy: this._overlay.position().global().centerHorizontally().centerVertically(),
      scrollStrategy: this._overlay.scrollStrategies.block(),
      disposeOnNavigation: true
    };

    // const galleryRef = this._gallery.ref(id);
    // galleryRef.set(i);

    this._overlayRef = this._overlay.create(overlayConfig);

    // overlay opened event
    this._overlayRef.attachments().subscribe(() => this.opened.next(id));

    // overlay closed event
    this._overlayRef.detachments().subscribe(() => this.closed.next(id));

    // Attach gallery to the overlay
    const galleryPortal = new ComponentPortal(LightboxComponent);
    const lightboxRef: ComponentRef<LightboxComponent> = this._overlayRef.attach(galleryPortal);

    if (_config.hasBackdrop) {
      this._overlayRef.backdropClick().subscribe(() => this.close());
    }

    // Add keyboard shortcuts
    if (_config.keyboardShortcuts) {
      this._overlayRef.keydownEvents().subscribe((event: any) => {
        switch (event.keyCode) {
          case LEFT_ARROW:
            // galleryRef.prev();
            break;
          case RIGHT_ARROW:
            // galleryRef.next();
            break;
          case ESCAPE:
            this.close();
        }
      });
    }
  }

  /**
   * Close Lightbox Overlay
   */
  close() {
    if (this._overlayRef.hasAttached()) {
      this._overlayRef.detach();
    }
  }
}


================================================
FILE: projects/ng-gallery/lightbox/src/public_api.ts
================================================
export * from './lightbox.model';
export * from './lightbox.component';
export * from './lightbox.service';
export * from './gallerize.directive';
// export * from './lightbox.directive';
export * from './lightbox.module';


================================================
FILE: projects/ng-gallery/ng-package.json
================================================
{
  "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
  "dest": "../../dist/ng-gallery",
  "lib": {
    "entryFile": "src/public-api.ts"
  }
}


================================================
FILE: projects/ng-gallery/package.json
================================================
{
  "name": "ng-gallery",
  "version": "13.0.0",
  "homepage": "https://ngx-gallery.netlify.app/",
  "author": {
    "name": "Murhaf Sousli",
    "url": "http://github.com/murhafsousli"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/murhafsousli/ngx-gallery.git"
  },
  "bugs": {
    "url": "http://github.com/murhafsousli/ngx-gallery/issues"
  },
  "keywords": [
    "gallery",
    "slider",
    "carousel",
    "lightbox",
    "images",
    "video",
    "modal"
  ],
  "license": "MIT",
  "peerDependencies": {
    "@angular/common": ">=19.0.0",
    "@angular/core": ">=19.0.0",
    "@angular/cdk": ">=19.0.0",
    "rxjs": ">=7.0.0"
  },
  "dependencies": {
    "tslib": "^2.3.1"
  }
}


================================================
FILE: projects/ng-gallery/src/lib/auto-height/auto-height.spec.ts
================================================
// import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';
// import { NoopAnimationsModule } from '@angular/platform-browser/animations';
// import { By } from '@angular/platform-browser';
// import { DebugElement } from '@angular/core';
// import { GalleryRef } from 'ng-gallery';
// import { getObservableFromContext, TestComponent } from './common';
// import { filter, firstValueFrom, fromEvent, Observable } from 'rxjs';
// import { AutoHeight } from '../observers/auto-height';
// import { ImgManager } from '../utils/img-manager';
//
// fdescribe('Auto-height directive', () => {
//   let fixture: ComponentFixture<TestComponent>;
//   let autoHeightDirective: AutoHeight;
//   let galleryRef: GalleryRef;
//   let manager: ImgManager;
//   let autoHeightDebugElement: DebugElement;
//
//   beforeEach(() => {
//     TestBed.configureTestingModule({
//       imports: [
//         NoopAnimationsModule,
//         TestComponent
//       ],
//       providers: [
//         { provide: ComponentFixtureAutoDetect, useValue: true }
//       ]
//     }).compileComponents();
//
//     fixture = TestBed.createComponent(TestComponent);
//     autoHeightDebugElement = fixture.debugElement.query(By.directive(AutoHeight));
//     autoHeightDirective = autoHeightDebugElement.injector.get(AutoHeight);
//     galleryRef = autoHeightDebugElement.injector.get(GalleryRef);
//     manager = autoHeightDebugElement.injector.get(ImgManager);
//     fixture.detectChanges();
//   });
//
//   it('should create [autoHeight] directive', () => {
//     expect(autoHeightDirective).toBeTruthy();
//   });
//
//   fit('should observe when items become visible as soon as possible', async () => {
//     TestBed.flushEffects();
//     await firstValueFrom(galleryRef.afterItemsVisible);
//
//     galleryRef.next('smooth');
//
//     const transitionEnd$ = fromEvent(autoHeightDebugElement.nativeElement, 'transitionend');
//
//     expect(autoHeightDirective.isResizing()).toBeTrue();
//
//     // const img: HTMLImageElement = await firstValueFrom(manager.getActiveItem());
//     // const el: HTMLElement = autoHeightDebugElement.nativeElement;
//     //
//     // await firstValueFrom(transitionEnd$);
//     // expect(autoHeightDirective.isResizing()).toBeFalse();
//     // expect(el.parentElement.parentElement.parentElement.clientHeight).toBe(img.naturalHeight);
//   });
// });


================================================
FILE: projects/ng-gallery/src/lib/auto-height/auto-height.ts
================================================
// import {
//   Directive,
//   effect,
//   inject,
//   signal,
//   untracked,
//   WritableSignal,
//   EffectCleanupRegisterFn
// } from '@angular/core';
// import {
//   Observable,
//   Subscription,
//   of,
//   filter,
//   fromEvent,
//   switchMap,
//   tap,
//   take,
//   debounceTime,
//   animationFrameScheduler
// } from 'rxjs';
// import { ImgManager } from '../utils/img-manager';
// import { GalleryComponent } from '../core/gallery.component';
// import { ResizeSensor } from '../services/resize-sensor';
//
// /**
//  * Auto height feature prerequisites:
//  * - autosize should be set to 'false'
//  * - if thumbnails exist, it should not be aligned to the right or left
//  */
//
// @Directive({
//   selector: 'gallery[autoHeight]',
//   host: {
//     '[attr.autoHeight]': 'true',
//     '[class.g-resizing]': 'isResizing()',
//     '[style.--slider-auto-height.px]': 'height()',
//   }
// })
// export class AutoHeight {
//
//   private readonly gallery: GalleryComponent = inject(GalleryComponent);
//
//   private readonly manager: ImgManager = inject(ImgManager);
//
//   readonly isResizing: WritableSignal<boolean> = signal(false);
//
//   readonly height: WritableSignal<number> = signal(0);
//
//   constructor() {
//     let sub$: Subscription;
//
//     let afterHeightChanged$: Observable<any>;
//
//     effect((onCleanup: EffectCleanupRegisterFn) => {
//       const resizeSensor: ResizeSensor = this.gallery.slider().resizeSensor();
//       // Check if height has transition for the auto-height feature
//       const transitionDuration: string = getComputedStyle(resizeSensor.nativeElement).transitionDuration;
//       if (!parseFloat(transitionDuration)) {
//         afterHeightChanged$ = of({});
//       } else {
//         console.log(transitionDuration)
//         afterHeightChanged$ = fromEvent(resizeSensor.nativeElement, 'transitionend');
//       }
//       // if (!this.galleryRef.config().autoHeight) return;
//       // const currIndex = this.galleryRef.currIndex();
//       untracked(() => {
//         sub$ = this.manager.getActiveItem().pipe(
//           filter((img: HTMLImageElement) => !!img),
//           // Wait for item image to be rendered
//           debounceTime(0, animationFrameScheduler),
//           // Skip if img height is equal the slider height
//           filter((img: HTMLImageElement) => {
//             console.log('🦕', resizeSensor.nativeElement.clientHeight, img.height)
//             return img.height !== resizeSensor.nativeElement.clientHeight
//           }),
//           switchMap((img: HTMLImageElement) => {
//             console.log('👽 Resize started! --slider-height', resizeSensor.nativeElement.clientHeight, img.height)
//             resizeSensor.disabled.set(true);
//             this.isResizing.set(true);
//
//             resizeSensor.nativeElement.style.setProperty('--slider-height', `${img.height}px`)
//
//             return afterHeightChanged$.pipe(
//               debounceTime(0, animationFrameScheduler),
//               tap(() => {
//                 resizeSensor.disabled.set(false);
//                 this.isResizing.set(false);
//               }),
//               take(1)
//             );
//           })
//         ).subscribe();
//
//         onCleanup(() => sub$?.unsubscribe());
//       });
//     });
//   }
// }


================================================
FILE: projects/ng-gallery/src/lib/autoplay/autoplay.directive.ts
================================================
// import {
//   Directive,
//   inject,
//   effect,
//   untracked,
//   EffectCleanupRegisterFn
// } from '@angular/core';
// import { Subscription, delay, of, switchMap, tap } from 'rxjs';
// import { ImgManager } from '../utils/img-manager';
// import { GalleryRef } from '../services/gallery-ref';
// import { GalleryConfig } from '../models/config.model';
//
// @Directive({
//   selector: 'gallery[autoplay]'
// })
// export class AutoplayDirective {
//
//   private _galleryRef: GalleryRef = inject(GalleryRef);
//
//   private _imgManager: ImgManager = inject(ImgManager);
//
//   constructor() {
//     let sub: Subscription;
//
//     // TODO: Should not observe config in the two effects, will be refactored
//     // TODO: Make especial inputs for the autoplay directive such as autoplayScrollBehavior
//
//     // effect((onCleanup: EffectCleanupRegisterFn) => {
//     //   const config: GalleryConfig = this._galleryRef.config();
//     //   const isPlaying: boolean = this._galleryRef.isPlaying();
//     //
//     //   untracked(() => {
//     //     if (isPlaying) {
//     //       sub = this._imgManager.getActiveItem().pipe(
//     //         switchMap(() => of({}).pipe(
//     //           delay(config.autoplayInterval),
//     //           tap(() => {
//     //             if (this._galleryRef.hasNext()) {
//     //               this._galleryRef.next(config.scrollBehavior);
//     //             } else {
//     //               this._galleryRef.set(0, config.scrollBehavior);
//     //             }
//     //           })
//     //         ))
//     //       ).subscribe();
//     //     }
//     //     onCleanup(() => sub?.unsubscribe());
//     //   });
//     // });
//
//     effect(() => {
//       const autoplay: boolean = this._galleryRef.config().autoplay;
//       untracked(() => autoplay ? this._galleryRef.play() : this._galleryRef.stop());
//     });
//   }
// }


================================================
FILE: projects/ng-gallery/src/lib/bullets/gallery-bullets.component.ts
================================================
import {
  Component,
  inject,
  numberAttribute,
  booleanAttribute,
  input,
  InputSignal,
  ChangeDetectionStrategy,
  InputSignalWithTransform
} from '@angular/core';
import { GalleryRef } from '../services/gallery-ref';
import { HorizontalPosition } from '../models/config.model';

@Component({
  selector: 'gallery-bullets',
  host: {
    '[attr.align]': 'align()',
    '[attr.disabled]': 'disabled()'
  },
  template: `
    @for (item of galleryRef.items(); track i; let i = $index) {
      <div class="g-bullet"
           [class.g-bullet-active]="i === galleryRef.currIndex()"
           [style.width.px]="size()"
           [style.height.px]="size()"
           (click)="disabled() ? null : galleryRef.set(i, scrollBehavior())">
        <div class="g-bullet-inner"></div>
      </div>
    }
  `,
  styleUrl: './gallery-bullets.scss',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class GalleryBulletsComponent {

  readonly galleryRef: GalleryRef = inject(GalleryRef);

  readonly scrollBehavior: InputSignal<ScrollBehavior> = input<ScrollBehavior>('smooth');

  /**
   * Align bullets
   */
  readonly align: InputSignal<HorizontalPosition> = input<HorizontalPosition>('top');

  /**
   * Disables thumbnails' clicks
   */
  readonly disabled: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(false, {
    transform: booleanAttribute
  });

  /**
   * Disables thumbnails' clicks
   */
  readonly size: InputSignalWithTransform<number, string | number> = input<number, string | number>(6, {
    transform: numberAttribute
  });
}


================================================
FILE: projects/ng-gallery/src/lib/bullets/gallery-bullets.scss
================================================
:host {
  position: absolute;
  left: 50%;
  z-index: 99;
  transform: translateX(-50%);
  display: flex;
  gap: 6px;
  top: var(--bullets-top);
  bottom: var(--bullets-bottom);

  // Gallery bullets variables
  --bullets-top: unset;
  --bullets-bottom: unset;
  --bullets-cursor: pointer;
  --bullets-opacity: 0.4;
  --bullets-hover-opacity: 1;
  --bullets-active-opacity: 1;

  &[align='top'] {
    --bullets-top: 15px;
  }

  &[align='bottom'] {
    --bullets-bottom: 15px;
  }

  &[disabled='true'] {
    --bullets-cursor: default;
    --bullets-hover-opacity: var(--bullets-opacity);
  }
}

:host,
.g-bullet,
.g-bullet-inner {
  display: flex;
  justify-content: center;
  align-items: center;
}

.g-bullet {
  cursor: var(--bullets-cursor);
  z-index: 20;

  &:hover .g-bullet-inner {
    opacity: var(--bullets-hover-opacity);
  }
}

.g-bullet-active .g-bullet-inner {
  opacity: var(--bullets-active-opacity);
}

.g-bullet-inner {
  background-color: var(--g-overlay-color);
  opacity: var(--bullets-opacity);
  width: 100%;
  height: 100%;
  border-radius: 50%;
  transition: opacity linear 150ms;
}


================================================
FILE: projects/ng-gallery/src/lib/bullets/gallery-bullets.spec.ts
================================================
import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { Component, DebugElement, Signal, viewChild } from '@angular/core';
import { By } from '@angular/platform-browser';
import {
  GalleryBulletsComponent,
  GalleryComponent,
  GalleryItemData,
  GalleryItemDef,
  GalleryRef,
  ImgRecognizer
} from 'ng-gallery';
import { img1, img2, img3 } from '../tests/test-images';

@Component({
  imports: [GalleryComponent, GalleryBulletsComponent, GalleryItemDef, ImgRecognizer],
  template: `
    <gallery [items]="items" [style.width.px]="width" [style.height.px]="height">
      <img *galleryItemDef="let item"
           galleryImage
           [src]="item.src"/>

      <gallery-bullets [align]="align" [disabled]="disabled" [size]="size" [scrollBehavior]="scrollBehavior"/>
    </gallery>
  `
})
export class TestComponent {
  items: GalleryItemData[] = [
    { src: img1 },
    { src: img2 },
    { src: img3 }
  ];
  width: number = 500;
  height: number = 300;

  align: 'top' | 'bottom' = 'top';
  disabled: boolean = false;
  size: number = 6;
  scrollBehavior: ScrollBehavior = 'smooth';

  gallery: Signal<GalleryComponent> = viewChild(GalleryComponent);
}

describe('Gallery bullets component', () => {
  let fixture: ComponentFixture<TestComponent>;
  let component: TestComponent;
  let bulletsComponent: GalleryBulletsComponent;
  let galleryRef: GalleryRef;
  let bulletsComponentElement: DebugElement;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        NoopAnimationsModule,
        TestComponent
      ],
      providers: [
        { provide: ComponentFixtureAutoDetect, useValue: true }
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(TestComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();

    bulletsComponentElement = fixture.debugElement.query(By.directive(GalleryBulletsComponent));
    bulletsComponent = bulletsComponentElement.injector.get(GalleryBulletsComponent);
    galleryRef = bulletsComponentElement.injector.get(GalleryRef);
  });

  it('should create gallery-bullets component', () => {
    expect(bulletsComponent).toBeTruthy();
    expect(galleryRef).toBeTruthy();
  });

  it('should set the align attribute', () => {
    expect(bulletsComponent.align()).toBe('top');
    expect((bulletsComponentElement.nativeElement as HTMLElement).getAttribute('align')).toBe('top');

    // Change attribute value
    component.align = 'bottom';
    fixture.detectChanges();

    expect(bulletsComponent.align()).toBe('bottom');
    expect((bulletsComponentElement.nativeElement as HTMLElement).getAttribute('align')).toBe('bottom');
  });

  it('should set the disabled attribute', () => {
    expect(bulletsComponent.disabled()).toBeFalse();
    expect((bulletsComponentElement.nativeElement as HTMLElement).getAttribute('disabled')).toBe('false');

    // Change attribute value
    component.disabled = true;
    fixture.detectChanges();

    expect(bulletsComponent.disabled()).toBeTrue();
    expect((bulletsComponentElement.nativeElement as HTMLElement).getAttribute('disabled')).toBe('true');
  });

  it('should set the size input', () => {
    expect(bulletsComponent.size()).toBe(6);

    // Change size value
    component.size = 10;
    fixture.detectChanges();

    expect(bulletsComponent.size()).toBe(10);
  });

  it('should set the scrollBehavior input', () => {
    expect(bulletsComponent.scrollBehavior()).toBe('smooth');

    // Change size value
    component.scrollBehavior = 'auto';
    fixture.detectChanges();

    expect(bulletsComponent.scrollBehavior()).toBe('auto');
  });
});


================================================
FILE: projects/ng-gallery/src/lib/core/gallery.component.ts
================================================
import {
  Component,
  inject,
  output,
  booleanAttribute,
  numberAttribute,
  computed,
  effect,
  untracked,
  input,
  viewChild,
  contentChild,
  Signal,
  InputSignal,
  TemplateRef,
  OutputEmitterRef,
  ChangeDetectionStrategy,
  InputSignalWithTransform
} from '@angular/core';
import { NgTemplateOutlet } from '@angular/common';
import { Directionality } from '@angular/cdk/bidi';
import { GalleryRef } from '../services/gallery-ref';
import { GALLERY_CONFIG, GalleryConfig, Orientation } from '../models/config.model';
import { BezierEasingOptions } from '../smooth-scroll';
import { GalleryItemContext, GalleryItemDef } from '../directives/gallery-item-def.directive';
import { GalleryBoxDef, GalleryStateContext } from '../directives/gallery-box-def.directive';
import { ImgManager } from '../utils/img-manager';
// import { AutoplayDirective } from '../autoplay/autoplay.directive';
import { GallerySliderComponent } from '../slider/gallery-slider.component';
import { GalleryItemData } from '../templates/items.model';

/**
 * Gallery component
 */
@Component({
  selector: 'gallery',
  host: {
    '[attr.dir]': 'dir.value',
    '[attr.debug]': 'debug()',
    '[attr.imageSize]': 'imageSize()',
    '[attr.orientation]': 'orientation()',
    '[attr.itemAutosize]': 'itemAutosize()',
    '[attr.scrollDisabled]': 'disableScroll()'
  },
  template: `
    <ng-content select="gallery-thumbs, gallery-bullets"/>

    <div class="g-box">

      <gallery-slider [class.g-debug]="debug()"
                      [template]="itemTemplate()"
                      (itemClick)="itemClick.emit($event)">
        <ng-content select="gallery-nav, gallery-counter"/>
      </gallery-slider>

      <div class="g-box-template">
        <!--        <ng-container *ngTemplateOutlet="boxTemplate(); context: { state: state(), config: config() }"/>-->
        <ng-container *ngTemplateOutlet="boxTemplate(); context: { config: config() }"/>
      </div>
    </div>
  `,
  styleUrls: ['./gallery.scss', '../debug/debug.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  // hostDirectives: [AutoplayDirective],
  imports: [GallerySliderComponent, NgTemplateOutlet],
  providers: [ImgManager, GalleryRef]
})
export class GalleryComponent {

  slider: Signal<GallerySliderComponent> = viewChild(GallerySliderComponent);

  /**
   * The gallery reference instance
   */
  readonly galleryRef: GalleryRef = inject(GalleryRef);


  readonly dir: Directionality = inject(Directionality);

  /**
   * @ignore
   */
  private _config: GalleryConfig = inject(GALLERY_CONFIG);

  /**
   * Loads the items array into the gallery
   */
  items: InputSignal<GalleryItemData[]> = input<GalleryItemData[]>();

  /**
   * Enables loop cycling
   */
  loop: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(this._config.loop, {
    transform: booleanAttribute
  });

  /**
   * Show visuals that helps debugging the component
   */
  debug: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(this._config.debug, {
    transform: booleanAttribute
  });

  /**
   * Centralize slider
   */
  centralized: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(this._config.centralized, {
    transform: booleanAttribute
  });

  /**
   * Fits each item size to its content, This option should be used with:
   * - Does not work if `autoHeight` is turned on
   * - Does not work properly unless `loadingAttr="eager"`
   * - Does not work properly unless `loadingStrategy="preload"`
   */
  itemAutosize: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(this._config.itemAutosize, {
    transform: booleanAttribute
  });

  /**
   * Automatically cycle through items at time interval
   */
  autoplay: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(this._config.autoplay, {
    transform: booleanAttribute
  });

  /**
   * Disables sliding using mousewheel, touchpad, scroll and gestures on touch devices
   */
  disableScroll: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(this._config.disableScroll, {
    transform: booleanAttribute
  });

  /**
   * Disables sliding using the mouse
   */
  disableMouseScroll: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(this._config.disableMouseScroll, {
    transform: booleanAttribute
  });

  /**
   * Sets the interval used for the autoplay feature
   */
  autoplayInterval: InputSignalWithTransform<number, string | number> = input<number, string | number>(this._config.autoplayInterval, {
    transform: numberAttribute
  });

  /**
   * Sets the duration used for smooth navigation between the items
   */
  scrollDuration: InputSignalWithTransform<number, string | number> = input<number, string | number>(this._config.scrollDuration, {
    transform: numberAttribute
  });

  /**
   * Sets the debounce time used to throttle the gallery update after it is resized
   */
  resizeDebounceTime: InputSignalWithTransform<number, string | number> = input<number, string | number>(this._config.resizeDebounceTime, {
    transform: numberAttribute
  });

  /**
   * Sets the scroll behavior when the active item is changed
   */
  scrollBehavior: InputSignal<ScrollBehavior> = input<ScrollBehavior>(this._config.scrollBehavior);

  /**
   * Sets the ease function used for smooth navigation between the items
   */
  scrollEase: InputSignal<BezierEasingOptions> = input<BezierEasingOptions>(this._config.scrollEase);

  /**
   * Sets the object-fit style applied on items' images
   */
  imageSize: InputSignal<'cover' | 'contain'> = input<'cover' | 'contain'>(this._config.imageSize);

  /**
   * Sets the sliding direction
   */
  orientation: InputSignal<Orientation> = input<Orientation>(this._config.orientation);

  /**
   * Skip initializing the config with components inputs (Lightbox mode)
   * This intended to be used and enabled from the lightbox component
   * @ignore
   */
  skipInitConfig: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(false, {
    transform: booleanAttribute
  });

  /**
   * Stream that emits when an item is clicked
   */
  itemClick: OutputEmitterRef<number> = output<number>();

  /**
   * Stream that emits when a thumbnail is clicked
   */
  thumbClick: OutputEmitterRef<number> = output<number>();

  /**
   * Stream that emits when player state is changed
   */
  // playingChange: OutputEmitterRef<GalleryState> = output<GalleryState>();

  /**
   * Stream that emits when index is changed
   */
  // indexChange: OutputEmitterRef<GalleryState> = output<GalleryState>();

  /**
   * Stream that emits when items array is changed
   */
  // itemsChange: OutputEmitterRef<GalleryState> = output<GalleryState>();

  /** @ignore */
  private _galleryItemDef: Signal<GalleryItemDef> = contentChild(GalleryItemDef);
  /** @ignore */
  private _galleryBoxDef: Signal<GalleryBoxDef> = contentChild(GalleryBoxDef);

  itemTemplate: Signal<TemplateRef<GalleryItemContext<GalleryItemData>>> = computed(() => this._galleryItemDef()?.templateRef)
  boxTemplate: Signal<TemplateRef<GalleryStateContext>> = computed(() => this._galleryBoxDef()?.templateRef)

  /** @ignore */
  config: Signal<GalleryConfig> = computed(() => {
    return {
      loop: this.loop(),
      debug: this.debug(),
      autoplay: this.autoplay(),
      imageSize: this.imageSize(),
      centralized: this.centralized(),
      scrollBehavior: this.scrollBehavior(),
      scrollEase: this.scrollEase(),
      autoplayInterval: this.autoplayInterval(),
      scrollDuration: this.scrollDuration(),
      orientation: this.orientation(),
      resizeDebounceTime: this.resizeDebounceTime(),
      disableScroll: this.disableScroll(),
      disableMouseScroll: this.disableMouseScroll(),
      itemAutosize: this.itemAutosize()
    };
  });

  constructor() {
    effect(() => {
      const config: GalleryConfig = this.config();
      untracked(() => this.galleryRef.setConfig(config));
    });

    effect(() => {
      const items: GalleryItemData[] = this.items();
      untracked(() => this.galleryRef.load(items));
    });
  }

  /**
   * Go to next item
   */
  next(behavior?: ScrollBehavior, loop?: boolean): void {
    this.galleryRef.next(behavior, loop);
  }

  /**
   * Go to prev item
   */
  prev(behavior?: ScrollBehavior, loop?: boolean): void {
    this.galleryRef.prev(behavior, loop);
  }

  /**
   * Set active item
   */
  set(i: number, behavior?: ScrollBehavior): void {
    this.galleryRef.set(i, behavior);
  }

  /**
   * Reset to initial state
   */
  reset(): void {
    this.galleryRef.reset();
  }

  /**
   * Start the player
   */
  play(interval?: number): void {
    this.galleryRef.play(interval);
  }

  /**
   * Stop the player
   */
  stop(): void {
    this.galleryRef.stop();
  }
}


================================================
FILE: projects/ng-gallery/src/lib/core/gallery.scss
================================================
:host {
  --g-height-transition: height 468ms cubic-bezier(0.42, 0, 0.58, 1);
  --g-nav-drop-shadow: drop-shadow(0 0 2px rgba(0, 0, 0, 0.6));
  --g-box-shadow: 0 0 3px rgba(0, 0, 0, 0.6);
  --g-font-color: #000;
  --g-overlay-color: #fff;
  --g-gutter-size: 0px;
  //--g-gutter-size: 1px;

  z-index: 1;
  position: relative;
  overflow: hidden;

  gap: var(--g-gutter-size);
  width: 100%;
  //width: 776px;
  height: 500px;
  min-height: 100%;
  max-height: 100%;
  background-color: black;

  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: auto minmax(0, 1fr) auto;
  grid-template-areas:
    ". top ."
    "left center right"
    ". bottom .";


  // Gallery items variables
  --g-item-width: 100%;
  --g-item-height: 100%;
  --g-item-max-height: var(--slider-height);

  &.g-resizing {
    // Changes the height of the slider to match the active item height
    //--slider-height: var(--slider-auto-height) !important;
    //::ng-deep {
    //  .g-slider {
    //    height: var(--slider-auto-height) !important;
    //  }
    //}
  }

  &[gallerize] {
    --g-item-cursor: pointer;
  }

  // Gallery auto-height variables
  &[autoHeight='true'][itemAutoSize='false'][orientation='horizontal'] {
    //&[thumbPosition='top'], &[thumbPosition='bottom'] {
      // if auto-height, use fit-content
      height: fit-content;
      --g-item-height: auto !important;
      --g-item-max-height: auto;
    //}
  }

  // Gallery image variables
  --image-object-fit: unset;

  &[imageSize='contain'] gallery-slider  {
    --image-object-fit: contain;
  }

  &[imageSize='cover'] gallery-slider  {
    --image-object-fit: cover;
  }

  // Gallery thumbs variables
  --slider-thumb-height: unset;
  --slider-thumb-width: unset;
  --thumb-slider-left: unset;
  --thumb-slider-overflow: unset;
  --thumb-slider-flex-direction: unset;
  --g-thumb-width: unset;
  --g-thumb-height: unset;
  --g-thumb-cursor: pointer;

  // Gallery slider variables
  --slider-scroll-snap-type: unset;
  --slider-overflow: unset;
  --slider-flex-direction: unset;
  --slider-width: unset;
  --slider-height: unset;
  --slider-content-width: unset;
  --slider-content-height: unset;

  &[orientation='horizontal'] {
    --slider-overflow: auto hidden;
    --slider-scroll-snap-type: x mandatory;
    --slider-flex-direction: row;
    --slider-content-height: 100%;
  }

  &[orientation='vertical'] {
    --slider-overflow: hidden auto;
    --slider-scroll-snap-type: y mandatory;
    --slider-flex-direction: column;
    --slider-content-width: 100%;
  }

  &[scrollDisabled='true'] {
    --slider-overflow: hidden !important;
  }

}

.g-box {
  grid-area: center;
  overflow: hidden;
  position: relative;
  display: flex;
  flex-direction: column;
}

.g-box-template {
  position: absolute;
  z-index: 10;
}


================================================
FILE: projects/ng-gallery/src/lib/core/gallery.spec.ts
================================================
import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { provideNoopAnimations } from '@angular/platform-browser/animations';
import { SliderItem } from '../slider/slider-item/slider-item';
import { TestComponent } from '../tests/common';
import { GalleryRef } from 'ng-gallery';


describe('Gallery component', () => {
  let fixture: ComponentFixture<TestComponent>;
  let component: TestComponent;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [TestComponent],
      providers: [
        provideNoopAnimations(),
        { provide: ComponentFixtureAutoDetect, useValue: true }
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(TestComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create gallery', () => {
    expect(component.gallery()).toBeTruthy();
  });

  it('should load and render items in the gallery', () => {
    expect(component.gallery().galleryRef.items()).toBe(component.items);
    const items: DebugElement[] = fixture.debugElement.queryAll(By.directive(SliderItem));
    expect(items.length).toBe(3);
  });

  it('should trigger forward galleryRef functions', () => {
    const galleryRef: GalleryRef = component.gallery().galleryRef;

    spyOn(component.gallery().galleryRef, 'next');
    component.gallery().next('auto', true);
    expect(galleryRef.next).toHaveBeenCalledOnceWith('auto', true);

    spyOn(component.gallery().galleryRef, 'prev');
    component.gallery().prev('auto', true);
    expect(galleryRef.prev).toHaveBeenCalledOnceWith('auto', true);

    spyOn(component.gallery().galleryRef, 'set');
    component.gallery().set(5, 'auto');
    expect(galleryRef.set).toHaveBeenCalledOnceWith(5, 'auto');

    spyOn(component.gallery().galleryRef, 'reset');
    component.gallery().reset();
    expect(galleryRef.reset).toHaveBeenCalled();

    spyOn(component.gallery().galleryRef, 'play');
    component.gallery().play(3000);
    expect(galleryRef.play).toHaveBeenCalledOnceWith(3000);

    spyOn(component.gallery().galleryRef, 'stop');
    component.gallery().stop();
    expect(galleryRef.stop).toHaveBeenCalled();
  });
});

// it('should trigger pan event', () => {
//   // Find the element
//   const pannableElement = fixture.debugElement.query(By.css('.pannable')).nativeElement;
//
//   // Create a mock Pan event
//   const panEvent = new Event('pan');
//   Object.assign(panEvent, {
//     deltaX: 100, // Pan distance in X axis
//     deltaY: 0,   // Pan distance in Y axis
//     type: 'pan',
//   });
//
//   // Dispatch the event
//   pannableElement.dispatchEvent(panEvent);
//
//   // Assert the expected behavior
//   expect(component.panEventTriggered).toBeTrue();
// });


================================================
FILE: projects/ng-gallery/src/lib/counter/gallery-counter.component.ts
================================================
import {
  Component,
  inject,
  computed,
  input,
  Signal,
  InputSignal,
  ChangeDetectionStrategy
} from '@angular/core';
import { GalleryRef } from '../services/gallery-ref';
import { HorizontalPosition } from '../models/config.model';

@Component({
  selector: 'gallery-counter',
  host: {
    '[attr.align]': 'align()'
  },
  template: `
    <div class="g-counter">{{ counter() }}</div>
  `,
  styleUrl: './gallery-counter.scss',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class GalleryCounterComponent {

  readonly galleryRef: GalleryRef = inject(GalleryRef);

  readonly align: InputSignal<HorizontalPosition> = input<HorizontalPosition>('top');

  readonly counter: Signal<string> = computed(() => {
    return `${ this.galleryRef.currIndex() + 1 } / ${ this.galleryRef.items().length }`;
  });
}


================================================
FILE: projects/ng-gallery/src/lib/counter/gallery-counter.scss
================================================
:host {
  // Gallery position variables
  --counter-top: unset;
  --counter-bottom: unset;
  --counter-border-radius: unset;

  &[align='top'] {
    --counter-top: 0;
    --counter-border-radius: 0 0 4px 4px;
  }

  &[align='bottom'] {
    --counter-bottom: 0;
    --counter-border-radius: 4px 4px 0 0;
  }
}

.g-counter {
  font-weight: bold;
  user-select: none;
  opacity: 0.6;
  transition: opacity linear 150ms;
  z-index: 50;
  position: absolute;
  left: 50%;
  transform: translateX(-50%) perspective(1px);
  font-size: 12px;
  padding: 4px 10px;
  color: var(--g-font-color);
  background-color: var(--g-overlay-color);
  box-shadow: var(--g-box-shadow);

  top: var(--counter-top);
  bottom: var(--counter-bottom);
  border-radius: var(--counter-border-radius);

  &:hover {
    opacity: 0.8;
  }
}


================================================
FILE: projects/ng-gallery/src/lib/counter/gallery-counter.spec.ts
================================================
import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { Component, DebugElement, Signal, viewChild } from '@angular/core';
import { By } from '@angular/platform-browser';
import { firstValueFrom } from 'rxjs';
import {
  GalleryComponent,
  GalleryCounterComponent,
  GalleryItemData,
  GalleryItemDef,
  GalleryRef,
  ImgRecognizer
} from 'ng-gallery';
import { img1, img2, img3 } from '../tests/test-images';
import { afterTimeout } from '../tests/common';

@Component({
  imports: [GalleryComponent, GalleryCounterComponent, GalleryItemDef, ImgRecognizer],
  template: `
    <gallery [items]="items" [style.width.px]="width" [style.height.px]="height">
      <img *galleryItemDef="let item"
           galleryImage
           [src]="item.src"/>

      <gallery-counter [align]="align"/>
    </gallery>
  `
})
export class TestComponent {
  items: GalleryItemData[] = [
    { src: img1 },
    { src: img2 },
    { src: img3 }
  ];
  width: number = 500;
  height: number = 300;

  align: 'top' | 'bottom' = 'top';

  gallery: Signal<GalleryComponent> = viewChild(GalleryComponent);
}

describe('Gallery counter component', () => {
  let fixture: ComponentFixture<TestComponent>;
  let component: TestComponent;
  let counterComponent: GalleryCounterComponent;
  let galleryRef: GalleryRef;
  let counterComponentElement: DebugElement;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        NoopAnimationsModule,
        TestComponent
      ],
      providers: [
        { provide: ComponentFixtureAutoDetect, useValue: true }
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(TestComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();

    counterComponentElement = fixture.debugElement.query(By.directive(GalleryCounterComponent));
    counterComponent = counterComponentElement.injector.get(GalleryCounterComponent);
    galleryRef = counterComponentElement.injector.get(GalleryRef);
  });

  it('should create gallery-counter component', () => {
    expect(counterComponent).toBeTruthy();
    expect(galleryRef).toBeTruthy();
  });

  it('should set the align attribute', () => {
    expect(counterComponent.align()).toBe('top');
    expect((counterComponentElement.nativeElement as HTMLElement).getAttribute('align')).toBe('top');

    // Change attribute value
    component.align = 'bottom';
    fixture.detectChanges();

    expect(counterComponent.align()).toBe('bottom');
    expect((counterComponentElement.nativeElement as HTMLElement).getAttribute('align')).toBe('bottom');
  });

  it('should calculate counter based on current index and total number of items', async () => {
    await firstValueFrom(galleryRef.afterItemsVisible);
    expect(counterComponent.counter()).toBe('1 / 3');

    component.gallery().next('auto');
    await afterTimeout(100);
    expect(counterComponent.counter()).toBe('2 / 3');
  });
});


================================================
FILE: projects/ng-gallery/src/lib/debug/debug.scss
================================================
:host[debug='true'] {
  ::ng-deep {
    .g-sliding, .g-resizing, .g-scrolling {
      gallery-item.g-item-highlight {
        //visibility: hidden;
      }
    }
  }

  ::ng-deep {
    gallery-slider {
      &:after, &:before {
        position: absolute;
        content: '';
        z-index: 12;
      }

      &:before {
        width: 100%;
        height: 0;
        border-top: 1px dashed lime;
      }

      &:after {
        height: 100%;
        width: 0;
        border-left: 1px dashed lime;
      }

      gallery-item {
        //outline: 1px solid darkorange;

        &.g-item-highlight {
          &:after {
            content: '';
            position: absolute;
            width: 100%;
            height: 100%;
            border: 3px solid lime;
            box-sizing: border-box;
            z-index: 10;
          }
        }
      }
    }

    .g-sliding {
      .g-slider-sliding {
        display: block;
      }
    }

    .g-scrolling {
      .g-slider-scrolling {
        display: block;
      }
    }

    .g-resizing {
      .g-slider-resizing {
        display: block;
      }
    }

    .g-slider-observed {
      display: block !important;
    }

    .g-slider-debug {
      position: absolute;
      top: 0;
      left: 0;
      display: flex;
      gap: 5px;
      padding: 10px;

      .g-slider-resizing {
        background: rgba(245, 76, 40);
      }

      .g-slider-scrolling {
        background: rgb(255, 133, 36);
      }

      .g-slider-sliding {
        background: rgb(31, 108, 185);
      }

      .g-slider-observed {
        background: rgb(31, 185, 139);
      }

      div, &:before {
        display: none;
        color: white;
        font-family: monospace;
        z-index: 12;
        padding: 2px 6px;
        border-radius: 3px;
      }
    }
  }

  &[itemAutoSize='false'] {
    ::ng-deep {
      .g-slider-debug {
        &:before {
          content: var(--intersection-margin);
          background: rgba(236, 236, 236, 0.84);
          color: #363636;
          display: block;
        }
      }
    }
  }

  &[itemAutoSize='true'] {
    ::ng-deep {
      gallery-item {
        &:before {
          position: absolute;
          margin: 10px;
          content: var(--item-intersection-margin);
          background: rgba(236, 236, 236, 0.84);
          color: #363636;
          display: block;
          width: 270px;
          font-family: monospace;
          z-index: 12;
          padding: 2px 6px;
          border-radius: 3px;
        }
      }
    }
  }
}


================================================
FILE: projects/ng-gallery/src/lib/directives/gallery-box-def.directive.spec.ts
================================================
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { Component, viewChild } from '@angular/core';
import { JsonPipe } from '@angular/common';
import { GalleryBoxDef, GalleryStateContext } from 'ng-gallery';

@Component({
  template: `
    <div *galleryBoxDef="let data">{{ data | json }}</div>
  `,
  imports: [GalleryBoxDef, JsonPipe]
})
class TestComponent {
  galleryBoxDef = viewChild(GalleryBoxDef);
}

describe('GalleryBoxDef Directive', () => {
  let fixture: ComponentFixture<TestComponent>;
  let component: TestComponent;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [GalleryBoxDef, TestComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(TestComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create the directive and inject the TemplateRef', () => {
    expect(component.galleryBoxDef()).toBeTruthy();
    expect(component.galleryBoxDef().templateRef).toBeDefined();
  });

  it('should guard the template context type', () => {
    const context: GalleryStateContext = {};
    expect(GalleryBoxDef.ngTemplateContextGuard(component.galleryBoxDef(), context)).toBeTrue();
  });
});


================================================
FILE: projects/ng-gallery/src/lib/directives/gallery-box-def.directive.ts
================================================
import { Directive, inject, TemplateRef } from '@angular/core';
import { GalleryConfig } from '../models/config.model';


@Directive({
  selector: '[galleryBoxDef]'
})
export class GalleryBoxDef {

  templateRef: TemplateRef<GalleryStateContext> = inject(TemplateRef<GalleryStateContext>);

  // Make sure the template checker knows the type of the context with which the
  // template of this directive will be rendered
  static ngTemplateContextGuard(
    directive: GalleryBoxDef,
    context: GalleryStateContext
  ): context is GalleryStateContext {
    return true;
  }
}

export interface GalleryStateContext {
  config?: GalleryConfig;
}


================================================
FILE: projects/ng-gallery/src/lib/directives/gallery-item-def.directive.spec.ts
================================================
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { Component, viewChild } from '@angular/core';
import { JsonPipe } from '@angular/common';
import { GalleryItemData, GalleryItemDef } from 'ng-gallery';
import { GalleryItemContext } from './gallery-item-def.directive';

@Component({
  template: `
    <div *galleryItemDef="let data">{{ data | json }}</div>
  `,
  imports: [GalleryItemDef, JsonPipe]
})
class TestComponent {
  galleryItemDef = viewChild(GalleryItemDef);
}

describe('GalleryItemDef Directive', () => {
  let fixture: ComponentFixture<TestComponent>;
  let component: TestComponent;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [GalleryItemDef, TestComponent]
    }).compileComponents();

    fixture = TestBed.createComponent(TestComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create the directive and inject the TemplateRef', () => {
    expect(component.galleryItemDef()).toBeTruthy();
    expect(component.galleryItemDef().templateRef).toBeDefined();
  });

  it('should guard the template context type', () => {
    const context: GalleryItemContext<GalleryItemData> = {} as GalleryItemContext<GalleryItemData>;

    expect(GalleryItemDef.ngTemplateContextGuard(component.galleryItemDef(), context)).toBeTrue();
  });
});


================================================
FILE: projects/ng-gallery/src/lib/directives/gallery-item-def.directive.ts
================================================
import { Directive, inject, TemplateRef } from '@angular/core';
import { GalleryItemData } from '../templates/items.model';

@Directive({
  selector: '[galleryItemDef]'
})
export class GalleryItemDef {
  templateRef: TemplateRef<GalleryItemContext<GalleryItemData>> = inject(TemplateRef<GalleryItemContext<GalleryItemData>>);

  // Make sure the template checker knows the type of the context with which the
  // template of this directive will be rendered
  static ngTemplateContextGuard(
    directive: GalleryItemDef,
    context: GalleryItemContext<GalleryItemData>
  ): context is GalleryItemContext<GalleryItemData> {
    return true;
  }
}

export interface GalleryItemContext<T> {
  /** Data for the row that this cell is located within. */
  $implicit?: T;

  /** Index of the item. */
  index?: number;

  /** True if this item is the active one. */
  active?: boolean;

  /** The number of total items. */
  count?: number;

  /** True if this item is first. */
  first?: boolean;

  /** True if this item is last. */
  last?: boolean;
}


================================================
FILE: projects/ng-gallery/src/lib/gallery.module.ts
================================================
import { NgModule } from '@angular/core';
import { GalleryComponent } from './core/gallery.component';
import { GalleryItemDef } from './directives/gallery-item-def.directive';
import { GalleryBoxDef } from './directives/gallery-box-def.directive';

@NgModule({
  imports: [
    GalleryComponent,
    GalleryItemDef,
    GalleryBoxDef
  ],
  exports: [
    GalleryComponent,
    GalleryItemDef,
    GalleryBoxDef
  ]
})
export class GalleryModule {
}


================================================
FILE: projects/ng-gallery/src/lib/gestures/hammer-slider.spec.ts
================================================
import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { TestComponent } from '../tests/common';
import { HammerSliding } from './hammer-sliding.directive';
import 'hammerjs';

describe('Hammer slider directive', () => {
  let fixture: ComponentFixture<TestComponent>;
  let hammerSliderElement: DebugElement
  let hammerSliderDirective: HammerSliding;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        NoopAnimationsModule,
        TestComponent
      ],
      providers: [
        { provide: ComponentFixtureAutoDetect, useValue: true }
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(TestComponent);
    fixture.detectChanges();

    hammerSliderElement = fixture.debugElement.query(By.directive(HammerSliding));
    hammerSliderDirective = hammerSliderElement.injector.get(HammerSliding);
  });

  it('should create [hammerSlider] directive', () => {
    expect(hammerSliderDirective).toBeTruthy();
  });

  // TODO: Failed to simulate sliding events
  // fit('should trigger panstart and set sliding to true',async () => {
  //   // Create a custom event with properties expected by HammerJS
  //   fixture.detectChanges();
  //   await afterTimeout(1000);
  //
  //   // Trigger the event
  //   nativeElement.dispatchEvent(new Event('pan'));
  //
  //   // Assert that the sliding signal is set to true
  //   expect(hammerSliderDirective.sliding()).toBeTrue();
  // });
});


================================================
FILE: projects/ng-gallery/src/lib/gestures/hammer-sliding.directive.ts
================================================
import {
  Directive,
  inject,
  signal,
  effect,
  untracked,
  booleanAttribute,
  input,
  NgZone,
  ElementRef,
  WritableSignal,
  EffectCleanupRegisterFn,
  InputSignalWithTransform
} from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { HammerGestureConfig } from '@angular/platform-browser';
import { Platform } from '@angular/cdk/platform';
import { Directionality } from '@angular/cdk/bidi';
import { take } from 'rxjs';

import { ORIENTATION } from '../models/constants';
import { GalleryRef } from '../services/gallery-ref';
import { GalleryConfig } from '../models/config.model';
import { SliderAdapter } from '../slider/adapters';
import { CustomHammerConfig, HammerInstance } from '../services/hammer';
import { createIntersectionObserver } from '../observers/intersection-observer';
import { SliderComponent } from '../slider/slider/slider';

@Directive({
  selector: '[hammerSliding]',
  host: {
    '[class.g-sliding]': 'sliding()'
  },
  providers: [{ provide: HammerGestureConfig, useClass: CustomHammerConfig }]
})
export class HammerSliding {

  private readonly hammer: HammerGestureConfig = inject(HammerGestureConfig);

  private readonly galleryRef: GalleryRef = inject(GalleryRef);

  private readonly _viewport: HTMLElement = inject(ElementRef<HTMLElement>).nativeElement;

  private readonly _document: Document = inject(DOCUMENT);

  private readonly _dir: Directionality = inject(Directionality);

  private readonly _platform: Platform = inject(Platform);

  private readonly _zone: NgZone = inject(NgZone);

  private readonly slider: SliderComponent = inject(SliderComponent, { self: true });

  sliding: WritableSignal<boolean> = signal<boolean>(false);

  isThumbs: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(false, {
    transform: booleanAttribute
  });

  constructor() {
    if (this._platform.ANDROID || this._platform.IOS || !(this._document.defaultView as any).Hammer) return;

    // HammerJS instance
    let mc: HammerInstance;

    effect((onCleanup: EffectCleanupRegisterFn) => {
      const config: GalleryConfig = this.galleryRef.config();
      const adapter: SliderAdapter = this.slider.adapter();

      if (!adapter && !config.disableMouseScroll) return;

      untracked(() => {
        this._zone.runOutsideAngular(() => {
          const direction: number = adapter.hammerDirection;

          this.hammer.overrides.pan = { direction };
          mc = this.hammer.buildHammer(this._viewport);

          let offset: number;

          // Set panOffset for sliding on pan start event
          mc.on('panstart', () => {
            this._zone.run(() => {
              this.sliding.set(true);
            });

            offset = adapter.scrollValue;
          });

          mc.on('panmove', (e: any) => {
            this._viewport.scrollTo(adapter.getHammerValue(offset, e, 'auto'));
          });

          mc.on('panend', (e: any) => {
            this._document.onselectstart = null;

            if (this.isThumbs()) {
              this.sliding.set(false);
              return;
            }

            const index: number = this.getIndexOnMouseUp(e, this.slider.adapter());
            if (index !== -1) {
              this._zone.run(() => {
                this.galleryRef.set(index);
                // Tiny delay is needed to avoid flicker positioning when scroll-snap is toggled
                // requestAnimationFrame(() => {
                  this.sliding.set(false);
                // });
              });
              return;
            }

            const visibleEntries: IntersectionObserverEntry[] = Object.values(this.slider.visibleEntries());

            const visibleElements: Element[] = visibleEntries.map((entry: IntersectionObserverEntry) => entry.target);

            // Get the diff between the viewport size and the smallest visible item size
            const diffSize: number = visibleEntries.reduce((total: number, entry: IntersectionObserverEntry) => {
              return Math.max(total, (this._viewport.clientWidth - entry.boundingClientRect.width) / 2);
            }, 0);

            const options: IntersectionObserverInit = {
              root: this._viewport,
              threshold: 0,
              rootMargin: `0px ${ -diffSize }px 0px ${ -diffSize }px`
            };

            createIntersectionObserver(options, visibleElements).pipe(
              take(1)
            ).subscribe((entries: IntersectionObserverEntry[]) => {

              const centerElement: IntersectionObserverEntry = entries
                .filter((entry: IntersectionObserverEntry) => entry.isIntersecting)
                .reduce((acc: IntersectionObserverEntry, entry: IntersectionObserverEntry) => {
                  return acc ? acc.intersectionRatio > entry.intersectionRatio ? acc : entry : entry;
                }, null);

              this._zone.run(() => {
                const index: number = +centerElement.target.getAttribute('galleryIndex');
                this.galleryRef.set(index);
                // Tiny delay is needed to avoid flicker positioning when scroll-snap is toggled
                // requestAnimationFrame(() => {
                  this.sliding.set(false);
                // });
              });
            })
          });
        });

        onCleanup(() => mc?.destroy());
      });
    });
  }

  private getIndexOnMouseUp(e: any, adapter: SliderAdapter): number {
    const currIndex: number = this.galleryRef.currIndex();

    const velocity: number = adapter.getHammerVelocity(e);
    // Check if velocity is great enough to navigate
    if (Math.abs(velocity) > 0.3) {
      if (this.galleryRef.config().orientation === ORIENTATION.Horizontal) {
        if (velocity > 0) {
          return this._dir.value === 'rtl' ? currIndex + 1 : currIndex - 1;
        }
        return this._dir.value === 'rtl' ? currIndex - 1 : currIndex + 1;
      } else {
        return velocity > 0 ? currIndex - 1 : currIndex + 1;
      }
    }

    // Reset position to the current index
    return -1;
  }
}


================================================
FILE: projects/ng-gallery/src/lib/gestures/mouse-sliding.directive.ts
================================================
// import { Directive, Inject, Input, Output, OnChanges, OnDestroy, SimpleChanges, NgZone, ElementRef, EventEmitter } from '@angular/core';
// import { DOCUMENT } from '@angular/common';
// import { Observable, Subscription, fromEvent, switchMap, take, takeUntil, tap } from 'rxjs';
// import { SliderAdapter } from '../components/adapters';
// import { GalleryConfig } from '../models/config.model';
// import { GalleryState } from '../models/gallery.model';
//
// @Directive({
//   selector: '[mouseSliding]',
//   standalone: true
// })
// export class MouseSliding implements OnChanges, OnDestroy {
//
//   private _currentSubscription: Subscription;
//
//   get _viewport(): HTMLElement {
//     return this._el.nativeElement;
//   }
//
//   @Input('mouseSliding') galleryId: string;
//
//   @Input() items: HTMLElement[];
//
//   @Input() adapter: SliderAdapter;
//
//   @Input() state: GalleryState;
//
//   @Input() config: GalleryConfig;
//
//   @Output() activeIndexChange: EventEmitter<number> = new EventEmitter<number>();
//
//   constructor(@Inject(DOCUMENT) private _document: Document,
//               private _zone: NgZone,
//               private _el: ElementRef<HTMLElement>) {
//   }
//
//
//   ngOnChanges(changes: SimpleChanges): void {
//     if (changes.config && changes.config.currentValue?.mouseScrollDisabled !== changes.config.previousValue?.mouseScrollDisabled) {
//       changes.config.currentValue.mouseScrollDisabled ? this._unsubscribe() : this._subscribe();
//     }
//   }
//
//   ngOnDestroy(): void {
//     this._unsubscribe();
//   }
//
//   private _subscribe(): void {
//     this._unsubscribe();
//
//     this._zone.runOutsideAngular(() => {
//       this._currentSubscription = this.dragEvent().subscribe();
//     });
//   }
//
//   private _unsubscribe(): void {
//     this._currentSubscription?.unsubscribe();
//   }
//
//   private dragEvent(): Observable<any> {
//     const mouseDown$: Observable<MouseEvent> = fromEvent<MouseEvent>(this._viewport, 'mousedown');
//     const mouseUp$: Observable<MouseEvent> = fromEvent<MouseEvent>(this._document, 'mouseup');
//     const mouseMove$: Observable<MouseEvent> = fromEvent<MouseEvent>(this._document, 'mousemove', {
//       capture: true,
//       passive: true
//     });
//
//     let offset: number;
//     let velocity: number;
//
//     const dragStart: Observable<MouseEvent> = mouseDown$.pipe(
//       tap((downEvent: MouseEvent) => {
//         downEvent.preventDefault();
//         offset = this.adapter.scrollValue;
//         this._viewport.style.scrollSnapType = 'unset';
//         this._viewport.classList.add('g-sliding');
//         this._document.onselectstart = () => false;
//       })
//     );
//
//     const dragEnd: Observable<MouseEvent> = mouseUp$.pipe(
//       tap((upEvent: MouseEvent) => {
//         this._document.onselectstart = null;
//         this._viewport.classList.remove('g-sliding');
//         const index: number = this.getIndexOnMouseUp(velocity);
//         this._zone.run(() => this.activeIndexChange.emit(index));
//       }),
//       take(1)
//     );
//
//     return dragStart.pipe(
//       switchMap((startEvent: MouseEvent) => {
//         return mouseMove$.pipe(
//           tap((moveEvent: MouseEvent) => {
//             const start: number = this.adapter.getDraggingProperty(startEvent);
//             const current: number = this.adapter.getDraggingProperty(moveEvent);
//             const deltaTime: number = moveEvent.timeStamp - startEvent.timeStamp;
//             const delta: number = current - start;
//             velocity = delta / deltaTime;
//             this._viewport.scrollTo(this.adapter.getDraggingValue(offset, delta, 'auto'));
//           }),
//           takeUntil(dragEnd)
//         );
//       })
//     );
//   }
//
//   private getIndexOnMouseUp(velocity: number): number {
//     // Check if scrolled item is great enough to navigate
//     const currElement: Element = this.items[this.state.currIndex];
//
//     // Find the gallery item element in the center elements
//     const elementAtCenter: Element = this.getElementFromViewportCenter();
//
//     // Check if center item can be taken from element using
//     if (elementAtCenter && elementAtCenter !== currElement) {
//       return +elementAtCenter.getAttribute('galleryIndex');
//     }
//
//     // Check if velocity is great enough to navigate
//     if (Math.abs(velocity) > 0.3) {
//       return velocity > 0 ? this.state.currIndex - 1 : this.state.currIndex + 1;
//     }
//
//     // Reset position to the current index
//     return -1;
//   }
//
//   private getElementFromViewportCenter(): Element {
//     // Get slider position relative to the document
//     const sliderRect: DOMRect = this._viewport.getBoundingClientRect();
//     // Try look for the center item using `elementsFromPoint` function
//     const centerElements: Element[] = this._document.elementsFromPoint(
//       sliderRect.x + (sliderRect.width / 2),
//       sliderRect.y + (sliderRect.height / 2)
//     );
//     // Find the gallery item element in the center elements
//     return centerElements.find((element: Element) => {
//       return element.getAttribute('galleryId') === this.galleryId;
//     });
//   }
// }


================================================
FILE: projects/ng-gallery/src/lib/models/config.model.ts
================================================
import { InjectionToken, Provider, TemplateRef } from '@angular/core';
import { BezierEasingOptions } from '../smooth-scroll';
import { defaultConfig } from '../utils/gallery.default';
import { GalleryItemData } from '../templates/items.model';

export const GALLERY_CONFIG: InjectionToken<GalleryConfig> = new InjectionToken<GalleryConfig>('GALLERY_CONFIG', {
  providedIn: 'root',
  factory: () => defaultConfig
});

export function provideGalleryOptions(options: GalleryConfig): Provider {
  return {
    provide: GALLERY_CONFIG,
    useValue: { ...defaultConfig, ...options }
  }
}

export type ImageSize = 'contain' | 'cover';

export type Orientation = 'horizontal' | 'vertical';

export type ThumbsPosition = 'top' | 'left' | 'right' | 'bottom';

export type HorizontalPosition = 'top' | 'bottom';

interface ThumbConfig {
  thumbLoadingIcon?: string;
  thumbLoadingError?: string;
}

interface NavConfig {
  navIcon?: string;
}

interface PlayerConfig {
  autoplay?: boolean;
  autoplayInterval?: number;
}

interface SliderConfig {
  loop?: boolean;
  disableScroll?: boolean;
  disableMouseScroll?: boolean;
  itemAutosize?: boolean;
  loadingIcon?: string;
  loadingError?: string;
  scrollDuration?: number;
  scrollEase?: BezierEasingOptions;
  orientation?: Orientation;
  imageSize?: ImageSize;
  centralized?: boolean;
}

export type GalleryConfig = SliderConfig
  & ThumbConfig
  & NavConfig
  & PlayerConfig
  & {
  scrollBehavior?: ScrollBehavior;
  resizeDebounceTime?: number;
  debug?: boolean;
}


================================================
FILE: projects/ng-gallery/src/lib/models/constants.ts
================================================
export enum IMAGE_SIZE {
  Cover = 'cover',
  Contain = 'contain'
}

export enum LOADING_STRATEGY {
  Preload = 'preload',
  Lazy = 'lazy',
  Default = 'default'
}

export enum LOADING_ATTR {
  Eager= 'eager',
  Lazy = 'lazy'
}

export enum THUMB_POSITION {
  Top = 'top',
  Left = 'left',
  Right = 'right',
  Bottom = 'bottom'
}

export enum BulletsPosition {
  Top = 'top',
  Bottom = 'bottom'
}

export enum CounterPosition {
  Top = 'top',
  Bottom = 'bottom'
}

export enum ORIENTATION {
  Horizontal = 'horizontal',
  Vertical = 'vertical'
}

export enum ITEM_TYPE {
  Image = 'image',
  Video = 'video',
  Youtube = 'youtube',
  Vimeo = 'vimeo',
  Iframe = 'iframe'
}

export type GalleryItemType = ITEM_TYPE | string;


================================================
FILE: projects/ng-gallery/src/lib/models/item.model.ts
================================================
export type ItemState = 'success' | 'loading' | 'failed';


================================================
FILE: projects/ng-gallery/src/lib/models/slider.model.ts
================================================
export interface SliderState {
  style: any;
  instant: boolean;
}

export interface WorkerState {
  value: number;
  instant: boolean;
}

export interface IndexChange {
  index: number;
  behavior: ScrollBehavior;
}


================================================
FILE: projects/ng-gallery/src/lib/models/styles.model.ts
================================================


================================================
FILE: projects/ng-gallery/src/lib/nav/gallery-nav.component.ts
================================================
import { Component, inject, computed, input, Signal, InputSignal, ChangeDetectionStrategy } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { Directionality } from '@angular/cdk/bidi';
import { GalleryRef } from '../services/gallery-ref';

@Component({
  host: {
    '[attr.dir]': 'dir.value'
  },
  selector: 'gallery-nav',
  template: `
    @if (galleryRef.config().loop || galleryRef.hasPrev()) {
      <i class="g-nav-prev"
         aria-label="Previous"
         role="button"
         (click)="galleryRef.prev(scrollBehavior())"
         [innerHtml]="navIcon()"></i>
    }
    @if (galleryRef.config().loop || galleryRef.hasNext()) {
      <i class="g-nav-next"
         aria-label="Next"
         role="button"
         (click)="galleryRef.next(scrollBehavior())"
         [innerHtml]="navIcon()"></i>
    }
  `,
  styleUrl: './gallery-nav.scss',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class GalleryNavComponent {

  readonly dir: Directionality = inject(Directionality);

  private readonly _sanitizer: DomSanitizer = inject(DomSanitizer);

  readonly galleryRef: GalleryRef = inject(GalleryRef);

  readonly navIcon: Signal<SafeHtml> = computed(() =>
    this._sanitizer.bypassSecurityTrustHtml(this.galleryRef.config().navIcon)
  );

  readonly scrollBehavior: InputSignal<ScrollBehavior> = input<ScrollBehavior>('smooth');

}


================================================
FILE: projects/ng-gallery/src/lib/nav/gallery-nav.scss
================================================
:host {
  // Gallery nav variables
  --nav-space: 8px;
  --nav-hover-space: 6.4px;
  --nav-next-right: unset;
  --nav-next-hover-right: unset;
  --nav-next-left: unset;
  --nav-next-hover-left: unset;

  &[dir='ltr'] {
    --nav-next-transform: translateY(-50%) perspective(1px);
    --nav-next-right: var(--nav-space);
    --nav-next-hover-right: var(--nav-hover-space);

    --nav-prev-transform: translateY(-50%) perspective(1px) scale(-1, -1);
    --nav-prev-left: var(--nav-space);
    --nav-prev-hover-left: var(--nav-hover-space);
  }

  &[dir='rtl'] {
    --nav-next-transform: translateY(-50%) perspective(1px) scale(-1, -1);
    --nav-next-left: var(--nav-space);
    --nav-next-hover-left: var(--nav-hover-space);

    --nav-prev-transform: translateY(-50%) perspective(1px);
    --nav-prev-right: var(--nav-space);
    --nav-prev-hover-right: var(--nav-hover-space);
  }
}

.g-nav-next,
.g-nav-prev {
  position: absolute;
  top: 50%;
  display: flex;
  padding: 16px 8px;
  cursor: pointer;
  z-index: 999;
  opacity: 0.6;
  transition: opacity linear 150ms, right linear 150ms, left linear 150ms;

  &:hover {
    opacity: 1;
  }

  ::ng-deep {
    svg {
      filter: var(--g-nav-drop-shadow);
      width: 28px;
      height: 28px;
      fill: #fff;
    }
  }
}

.g-nav-next {
  left: var(--nav-next-left);
  right: var(--nav-next-right);
  transform: var(--nav-next-transform);

  &:hover {
    left: var(--nav-next-hover-left);
    right: var(--nav-next-hover-right);
  }
}

.g-nav-prev {
  left: var(--nav-prev-left);
  right: var(--nav-prev-right);
  transform: var(--nav-prev-transform);

  &:hover {
    left: var(--nav-prev-hover-left);
    right: var(--nav-prev-hover-right);
  }
}


================================================
FILE: projects/ng-gallery/src/lib/nav/gallery-nav.spec.ts
================================================
import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { Component, DebugElement, Signal, viewChild } from '@angular/core';
import { By } from '@angular/platform-browser';
import {
  GalleryNavComponent,
  GalleryComponent,
  GalleryItemData,
  GalleryItemDef,
  GalleryRef,
  ImgRecognizer
} from 'ng-gallery';
import { img1, img2, img3 } from '../tests/test-images';
import { Dir, Direction } from '@angular/cdk/bidi';

@Component({
  imports: [GalleryComponent, Dir, GalleryNavComponent, GalleryItemDef, ImgRecognizer],
  template: `
    <gallery [dir]="dir" [items]="items" [style.width.px]="width" [style.height.px]="height">
      <img *galleryItemDef="let item"
           galleryImage
           [src]="item.src"/>

      <gallery-nav [scrollBehavior]="scrollBehavior"/>
    </gallery>
  `
})
export class TestComponent {
  items: GalleryItemData[] = [
    { src: img1 },
    { src: img2 },
    { src: img3 }
  ];
  width: number = 500;
  height: number = 300;

  scrollBehavior: ScrollBehavior = 'smooth';
  dir: Direction = 'ltr';

  gallery: Signal<GalleryComponent> = viewChild(GalleryComponent);
}

describe('Gallery nav component', () => {
  let fixture: ComponentFixture<TestComponent>;
  let component: TestComponent;
  let navComponent: GalleryNavComponent;
  let galleryRef: GalleryRef;
  let navComponentElement: DebugElement;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        NoopAnimationsModule,
        TestComponent
      ],
      providers: [
        { provide: ComponentFixtureAutoDetect, useValue: true }
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(TestComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();

    navComponentElement = fixture.debugElement.query(By.directive(GalleryNavComponent));
    navComponent = navComponentElement.injector.get(GalleryNavComponent);
    galleryRef = navComponentElement.injector.get(GalleryRef);
  });

  it('should create gallery-nav component', () => {
    expect(navComponent).toBeTruthy();
    expect(galleryRef).toBeTruthy();
    expect(navComponent.dir).toBeTruthy();
    expect(navComponent.navIcon()).toBeTruthy();
  });

  it('should set dir attribute', () => {
    expect((navComponentElement.nativeElement as HTMLElement).getAttribute('dir')).toBe('ltr');

    component.dir = 'rtl';
    fixture.detectChanges();

    expect((navComponentElement.nativeElement as HTMLElement).getAttribute('dir')).toBe('rtl');
  });

  it('should set the scrollBehavior input', () => {
    expect(navComponent.scrollBehavior()).toBe('smooth');

    // Change size value
    component.scrollBehavior = 'auto';
    fixture.detectChanges();

    expect(navComponent.scrollBehavior()).toBe('auto');
  });
});


================================================
FILE: projects/ng-gallery/src/lib/observers/intersection-directive.spec.ts
================================================
import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { GalleryRef } from 'ng-gallery';
import { getObservableFromContext, TestComponent } from '../tests/common';
import { IntersectionSensor } from './intersection-sensor.directive';
import { filter, firstValueFrom, Observable } from 'rxjs';

describe('Intersection directive', () => {
  let fixture: ComponentFixture<TestComponent>;
  let intersectionSensorDirective: IntersectionSensor;
  let galleryRef: GalleryRef;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        NoopAnimationsModule,
        TestComponent
      ],
      providers: [
        { provide: ComponentFixtureAutoDetect, useValue: true }
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(TestComponent);
    fixture.detectChanges();

    const intersectionSensorElement: DebugElement = fixture.debugElement.query(By.directive(IntersectionSensor));
    intersectionSensorDirective = intersectionSensorElement.injector.get(IntersectionSensor);
    galleryRef = intersectionSensorElement.injector.get(GalleryRef);
  });

  it('should create [intersectionSensor] directive', () => {
    expect(intersectionSensorDirective).toBeTruthy();
  });

  it('should observe when items become visible as soon as possible', async () => {
    await firstValueFrom(galleryRef.afterItemsVisible);

    const visibleItems: Record<number, IntersectionObserverEntry> = galleryRef.visibleItems();
    const element: Element = visibleItems[0].target;
    const queryElement: DebugElement = fixture.debugElement.query(By.css('slider-item.g-item-highlight'));

    expect(Object.keys(visibleItems).length).toBe(1);
    expect(element).toBe(queryElement.nativeElement);
    expect(element.classList.contains('g-item-highlight')).toBe(true);
    expect(galleryRef.currIndex()).toBe(0);
  });

  it('should detect when next item becomes visible on scroll then detect the previous leave after scroll', async () => {
    await firstValueFrom(galleryRef.afterItemsVisible);

    expect(galleryRef.currIndex()).toBe(0);
    galleryRef.next();

    // Wait for scroll starts and the next item is detected, at this point both previous and next items are visible

    const visibleItemIsTwo$: Observable<any> = getObservableFromContext(galleryRef.visibleItems).pipe(
      filter((obj: Record<number, IntersectionObserverEntry>) => Object.keys(obj).length === 2)
    );
    await firstValueFrom(visibleItemIsTwo$);

    const visibleItems: Record<number, IntersectionObserverEntry> = galleryRef.visibleItems();
    const queryElements: DebugElement[] = fixture.debugElement.queryAll(By.css('slider-item.g-item-highlight'));

    expect(Object.keys(visibleItems).length).toBe(2);
    expect(visibleItems[0].target).toBe(queryElements[0].nativeElement);
    expect(visibleItems[1].target).toBe(queryElements[1].nativeElement);

    // Wait until scroll is ended and the new active item is set

    const arrivedToNextItem$: Observable<any> = galleryRef.indexChanged.pipe(
      filter((currIndex: number) => currIndex === 1)
    );
    await firstValueFrom(arrivedToNextItem$);

    const visibleItemsAfter: Record<number, IntersectionObserverEntry> = galleryRef.visibleItems();
    const queryElementsAfter: DebugElement[] = fixture.debugElement.queryAll(By.css('slider-item.g-item-highlight'));

    expect(Object.keys(visibleItems).length).toBe(1);
    expect(visibleItemsAfter[1].target).toBe(queryElementsAfter[0].nativeElement);
    expect(galleryRef.currIndex()).toBe(1);
  });
});


================================================
FILE: projects/ng-gallery/src/lib/observers/intersection-observer.ts
================================================
import { Observable, Subscriber } from 'rxjs';

export function createIntersectionObserver(options: IntersectionObserverInit, elements: Element[]): Observable<IntersectionObserverEntry[]> {
  return new Observable((observer: Subscriber<IntersectionObserverEntry[]>) => {
    const intersectionObserver: IntersectionObserver = new IntersectionObserver(
      (entries: IntersectionObserverEntry[]) => observer.next(entries),
      options
    );
    elements.forEach((element: HTMLElement) => intersectionObserver.observe(element));
    return () => {
      elements.forEach((element: HTMLElement) => intersectionObserver.unobserve(element));
      intersectionObserver.disconnect();
    };
  });
}



================================================
FILE: projects/ng-gallery/src/lib/observers/intersection-sensor.directive.ts
================================================
import {
  Directive,
  inject,
  effect,
  computed,
  untracked,
  Signal,
  NgZone,
  ElementRef,
  EffectCleanupRegisterFn
} from '@angular/core';
import { Subscription } from 'rxjs';
import { GalleryConfig } from '../models/config.model';
import { GalleryRef } from '../services/gallery-ref';
import { SliderAdapter } from '../slider/adapters';
import { SliderItem } from '../slider/slider-item/slider-item';
import { createIntersectionObserver } from './intersection-observer';
import { SmoothScroll } from '../smooth-scroll';
import { HammerSliding } from '../gestures/hammer-sliding.directive';
import { SliderComponent } from '../slider/slider/slider';

/**
 * This observer used to detect when a slider element reaches the active soon
 */
@Directive({
  selector: '[intersectionSensor]'
})
export class IntersectionSensor {

  private readonly zone: NgZone = inject(NgZone);

  private readonly galleryRef: GalleryRef = inject(GalleryRef);

  private readonly smoothScroll: SmoothScroll = inject(SmoothScroll);

  private readonly hammerSlider: HammerSliding = inject(HammerSliding);

  private readonly nativeElement: HTMLElement = inject(ElementRef<HTMLElement>).nativeElement;

  private readonly slider: SliderComponent = inject(SliderComponent, { self: true });

  readonly disableInteractionObserver: Signal<boolean> = computed(() => {
    return this.smoothScroll.scrolling() || this.hammerSlider.sliding(); // || this.resizeSensor.isResizing();
  });

  constructor() {
    let visibleItemsObserver$: Subscription;
    let activeItemObserver$: Subscription;

    effect((onCleanup: EffectCleanupRegisterFn) => {
      const config: GalleryConfig = this.galleryRef.config();
      const items: ReadonlyArray<SliderItem> = this.slider.items();
      const adapter: SliderAdapter = this.slider.adapter();

      if (!adapter || !items.length) return;

      untracked(() => {
        const rootMargin: string = adapter.getRootMargin();
        if (config.debug) {
          this.nativeElement.style.setProperty('--intersection-margin', `"INTERSECTION(${ rootMargin })"`);
        }

        this.zone.runOutsideAngular(() => {
          const options: IntersectionObserverInit = { root: this.nativeElement, threshold: 0.1, rootMargin };
          const elements: HTMLElement[] = items.map((item: SliderItem) => item.nativeElement);

          visibleItemsObserver$ = createIntersectionObserver(options, elements).subscribe((entries: IntersectionObserverEntry[]) => {
            const visibleItems: Record<number, IntersectionObserverEntry> = this.slider.visibleEntries();
            entries.forEach((entry: IntersectionObserverEntry) => {
              if (entry.isIntersecting) {
                entry.target.classList.add('g-item-highlight');
                visibleItems[+entry.target.getAttribute('galleryIndex')] = entry;
              } else {
                entry.target.classList.remove('g-item-highlight');
                delete visibleItems[+entry.target.getAttribute('galleryIndex')];
              }
            });
            this.zone.run(() => {
              this.galleryRef.afterItemsVisible.next();
              this.slider.visibleEntries.set({ ...visibleItems });
            });
          });
        });

        onCleanup(() => visibleItemsObserver$?.unsubscribe());
      });
    });

    effect((onCleanup) => {
      const disabled: boolean = this.disableInteractionObserver();
      const visibleElements: IntersectionObserverEntry[] = Object.values(this.slider.visibleEntries());

      if (disabled) return;

      // TODO: Should handle vertical orientation
      untracked(() => {
        const elements: Element[] = visibleElements.map((entry: IntersectionObserverEntry) => entry.target);

        // Get the diff between the viewport size and the smallest visible item size
        const diffSize: number = visibleElements.reduce((total: number, entry: IntersectionObserverEntry) => {
          return Math.min(total, (this.nativeElement.clientWidth - entry.boundingClientRect.width) / 2);
        }, 0);

        const options: IntersectionObserverInit = {
          root: this.nativeElement,
          threshold: .999,
          rootMargin: `1000px ${ -diffSize }px 1000px ${ -diffSize }px`
        };

        this.zone.runOutsideAngular(() => {
          activeItemObserver$ = createIntersectionObserver(options, elements).subscribe((entries: IntersectionObserverEntry[]) => {

            const elementWithHighestIntersectionRatio: IntersectionObserverEntry = entries
              .filter((entry: IntersectionObserverEntry) => entry.isIntersecting)
              .reduce((acc: IntersectionObserverEntry, entry: IntersectionObserverEntry) => {
                return acc ? acc.intersectionRatio > entry.intersectionRatio ? acc : entry : entry;
              }, null);

            if (!elementWithHighestIntersectionRatio) return;

            const index: number = +elementWithHighestIntersectionRatio.target.getAttribute('galleryIndex');

            // TODO: There is a bug where index becomes 1 then goes back to 0
            if (index === this.galleryRef.currIndex()) return;

            // Set the new current index
            this.zone.run(() => this.galleryRef.currIndex.set(index));
          });
        });

        onCleanup(() => activeItemObserver$?.unsubscribe());
      });
    });
  }
}


================================================
FILE: projects/ng-gallery/src/lib/services/gallery-ref.ts
================================================
import { Injectable, computed, inject, signal, Signal, WritableSignal } from '@angular/core';
import { toObservable } from '@angular/core/rxjs-interop';
import { Observable, Subject } from 'rxjs';
import { GALLERY_CONFIG, GalleryConfig } from '../models/config.model';
import { GalleryItemData } from '../templates/items.model';
import { IndexChange } from '../models/slider.model';

@Injectable()
export class GalleryRef {

  readonly afterItemsVisible: Subject<void> = new Subject();

  /** Stream that emits on item click */
  readonly itemClick: Subject<number> = new Subject<number>();

  /** Stream that emits on thumbnail click */
  readonly thumbClick: Subject<number> = new Subject<number>();

  /** Stream that emits when items is changed (items loaded, item added, item removed) */
  readonly itemsChanged: Subject<void> = new Subject<void>();

  /** Gallery Events */

  readonly visibleItems: WritableSignal<Record<number, IntersectionObserverEntry>> = signal({});

  readonly visibleThumbs: WritableSignal<Record<number, IntersectionObserverEntry>> = signal({});

  readonly items: WritableSignal<GalleryItemData[]> = signal([]);

  readonly currIndex: WritableSignal<number> = signal(0);

  readonly isPlaying: WritableSignal<boolean> = signal(false);

  readonly scrollBehavior: WritableSignal<ScrollBehavior> = signal(null);

  readonly hasNext: Signal<boolean> = computed(() => this.currIndex() < this.items().length - 1);

  readonly hasPrev: Signal<boolean> = computed(() => this.currIndex() > 0);

  readonly indexChange: Subject<IndexChange> = new Subject<IndexChange>();

  /** Stream that emits when current index is changed */
  readonly indexChanged: Observable<number> = toObservable(this.currIndex);

  /** Config signal */
  readonly config: WritableSignal<GalleryConfig> = signal(inject(GALLERY_CONFIG));

  /** Stream that emits when the player should start or stop */
  readonly playingChanged: Observable<boolean> = toObservable(this.isPlaying);

  setConfig(newConfig: GalleryConfig): void {
    this.config.update((config: GalleryConfig) => {
      return { ...config, ...newConfig };
    });
  }

  /**
   * Add gallery item
   */
  add(newItem: GalleryItemData, active?: boolean): void {
    this.items.update((items: GalleryItemData[]) => {
      return [...items, newItem];
    });
    if (active) {
      this.currIndex.set(this.items().length - 1);
    }
    this.itemsChanged.next();
  }

  /**
   * Remove gallery item
   */
  remove(i: number): void {
    this.items.update((items: GalleryItemData[]) => {
      return [
        ...items.slice(0, i),
        ...items.slice(i + 1, items.length)
      ];
    });
    this.currIndex.update((currIndex: number): number => i < 1 ? currIndex : i - 1);
    this.itemsChanged.next();
  }

  /**
   * Load items and reset the state
   */
  load(items: GalleryItemData[]): void {
    if (items) {
      this.items.set(items);
      this.itemsChanged.next();
    }
  }

  /**
   * Set active item
   */
  set(i: number, behavior?: ScrollBehavior): void {
    if (i < 0 || i >= this.items().length) {
      console.error(`[NgGallery]: Unable to set the active item because the given index (${ i }) is outside the items range!`);
      return;
    }
    // this.currIndex.set(i);
    if (behavior) {
      this.scrollBehavior.set(behavior);
    }
    this.indexChange.next({ index: i, behavior });
  }

  /**
   * Next item
   */
  next(behavior?: ScrollBehavior, loop: boolean = true): void {
    if (this.hasNext()) {
      this.set(this.currIndex() + 1, behavior);
    } else if (loop && this.config().loop) {
      this.set(0, behavior);
    }
  }

  /**
   * Prev item
   */
  prev(behavior?: ScrollBehavior, loop: boolean = true): void {
    if (this.hasPrev()) {
      this.set(this.currIndex() - 1, behavior);
    } else if (loop && this.config().loop) {
      this.set(this.items().length - 1, behavior);
    }
  }

  /**
   * Start gallery player
   */
  play(interval?: number): void {
    if (interval) {
      this.config.update((config: GalleryConfig) => {
        return { ...config, autoplayInterval: interval };
      });
    }
    this.isPlaying.set(true);
  }

  /**
   * Stop gallery player
   */
  stop(): void {
    this.isPlaying.set(false);
  }

  /**
   * Reset gallery to initial state
   */
  reset(): void {
    this.items.set([]);
  }

}


================================================
FILE: projects/ng-gallery/src/lib/services/hammer.ts
================================================
import { Injectable } from '@angular/core';
import { HammerGestureConfig } from '@angular/platform-browser';

export interface HammerInstance {
  on(eventName: string, callback?: Function): void;
  off(eventName: string, callback?: Function): void;
  destroy?(): void;
}

declare const Hammer: any;

@Injectable()
export class CustomHammerConfig extends HammerGestureConfig {
  override overrides = {
    pinch: { enable: false },
    rotate: { enable: false }
  };

  override options = { inputClass: typeof Hammer !== 'undefined' ? Hammer.MouseInput : null };
}


================================================
FILE: projects/ng-gallery/src/lib/services/resize-directive.spec.ts
================================================
import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { GalleryRef } from 'ng-gallery';
import { firstValueFrom } from 'rxjs';
import { afterTimeout, TestComponent } from '../tests/common';
import { SliderComponent } from '../slider/slider/slider';
import { ResizeSensor } from './resize-sensor';

describe('Resize sensor directive', () => {
  let fixture: ComponentFixture<TestComponent>;
  let component: TestComponent;
  let resizeSensorDirective: ResizeSensor;
  let sliderComponent: SliderComponent;
  let galleryRef: GalleryRef;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        NoopAnimationsModule,
        TestComponent
      ],
      providers: [
        { provide: ComponentFixtureAutoDetect, useValue: true }
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(TestComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();

    const resizeSensorElement: DebugElement = fixture.debugElement.query(By.directive(ResizeSensor));
    resizeSensorDirective = resizeSensorElement.injector.get(ResizeSensor);
    galleryRef = resizeSensorElement.injector.get(GalleryRef);

    const sliderComponentElement: DebugElement = fixture.debugElement.query(By.directive(SliderComponent));
    sliderComponent = sliderComponentElement.componentInstance;
  });

  it('should create [resizeSensor] directive', () => {
    expect(resizeSensorDirective).toBeTruthy();
  });

  it('should compute "centralizeStart" size when content >= viewport', async () => {
    await firstValueFrom(galleryRef.afterItemsVisible);
    expect(resizeSensorDirective.centralizeStart()).toBe(0);
    expect(resizeSensorDirective.centralizeStart()).toBe(0);
    expect(sliderComponent.nativeElement.style.getPropertyValue('--centralize-start-size')).toBe('0px');
    expect(sliderComponent.nativeElement.style.getPropertyValue('--centralize-end-size')).toBe('0px');
  });

  it('should compute "centralizeStart" size when content < viewport', async () => {
    await firstValueFrom(galleryRef.afterItemsVisible);
    sliderComponent.galleryRef.setConfig({
      itemAutosize: true,
      centralized: true
    });
    component.width = 800;
    component.height = 200;

    fixture.detectChanges();

    expect(resizeSensorDirective.centralizeStart()).toBe(100);
    expect(resizeSensorDirective.centralizeStart()).toBe(100);
    expect(sliderComponent.nativeElement.style.getPropertyValue('--centralize-start-size')).toBe('100px');
    expect(sliderComponent.nativeElement.style.getPropertyValue('--centralize-end-size')).toBe('100px');
  });

  it('should compute "centralizeStart" size when content >= viewport', async () => {
    await firstValueFrom(galleryRef.afterItemsVisible);
    expect(resizeSensorDirective.centralizeStart()).toBe(0);
    expect(resizeSensorDirective.centralizeStart()).toBe(0);
    expect(sliderComponent.nativeElement.style.getPropertyValue('--centralize-start-size')).toBe('0px');
    expect(sliderComponent.nativeElement.style.getPropertyValue('--centralize-end-size')).toBe('0px');
  });

  it('should update the size signal when component size changes', async () => {
    await firstValueFrom(galleryRef.afterItemsVisible);
    expect(resizeSensorDirective.slideSize().width).toBe(500);
    expect(resizeSensorDirective.slideSize().height).toBe(300);
    expect(sliderComponent.nativeElement.style.getPropertyValue('--slider-width')).toBe('500px');
    expect(sliderComponent.nativeElement.style.getPropertyValue('--slider-height')).toBe('300px');

    component.width = 400;
    fixture.detectChanges();
    await afterTimeout(40);

    expect(resizeSensorDirective.slideSize().width).toBe(400);
    expect(sliderComponent.nativeElement.style.getPropertyValue('--slider-width')).toBe('400px');
  });

});


================================================
FILE: projects/ng-gallery/src/lib/services/resize-sensor.ts
================================================
import {
  Directive,
  signal,
  inject,
  computed,
  untracked,
  afterRenderEffect,
  NgZone,
  Signal,
  ElementRef,
  WritableSignal,
  EffectCleanupRegisterFn
} from '@angular/core';
import { SharedResizeObserver } from '@angular/cdk/observers/private';
import { Subscription, animationFrameScheduler, throttleTime, combineLatest } from 'rxjs';
import { GalleryConfig } from '../models/config.model';
import { GalleryRef } from './gallery-ref';
import { SliderComponent } from '../slider/slider/slider';

@Directive({
  selector: '[resizeSensor]',
  host: {
    '[style.--slider-width.px]': 'slideSize()?.width',
    '[style.--slider-height.px]': 'slideSize()?.height',
    '[style.--centralize-start-size.px]': 'centralizeStart()',
    '[style.--centralize-end-size.px]': 'centralizeEnd()'
  }
})
export class ResizeSensor {

  nativeElement:HTMLElement = inject(ElementRef).nativeElement;

  private readonly sharedResizeObserver: SharedResizeObserver = inject(SharedResizeObserver);

  private readonly zone: NgZone = inject(NgZone);

  private readonly galleryRef: GalleryRef = inject(GalleryRef);

  private readonly slider: SliderComponent = inject(SliderComponent, { self: true });

  readonly slideSize: WritableSignal<DOMRectReadOnly> = signal(null);

  readonly contentSize: WritableSignal<DOMRectReadOnly> = signal(null);

  readonly undefinedSizes: Signal<boolean> = computed(() => !this.slideSize() || !this.contentSize());

  readonly centralizeStart: Signal<number> = computed(() => {
    if (this.undefinedSizes()) return 0;
    return this.slider.adapter()?.getCentralizerStartSize();
  });

  readonly centralizeEnd: Signal<number> = computed(() => {
    if (this.undefinedSizes()) return 0;
    return this.slider.adapter()?.getCentralizerEndSize();
  });

  disabled: WritableSignal<boolean> = signal(false);

  constructor() {
    let resizeSubscription$: Subscription;

    afterRenderEffect({
      earlyRead: (onCleanup: EffectCleanupRegisterFn) => {
        const config: GalleryConfig = this.galleryRef.config();

        // Make sure items are rendered
        if (!this.slider.items().length || this.disabled()) return;

        untracked(() => {
          this.zone.runOutsideAngular(() => {
            resizeSubscription$ = combineLatest([
              this.sharedResizeObserver.observe(this.slider.nativeElement),
              this.sharedResizeObserver.observe(this.slider.nativeElement.firstElementChild)
            ]).pipe(
              throttleTime(config.resizeDebounceTime, animationFrameScheduler, {
                leading: true,
                trailing: true
              }),
            ).subscribe(([sliderEntries, contentEntries]: [ResizeObserverEntry[], ResizeObserverEntry[]]) => {
              this.zone.run(() => {
                if (!sliderEntries || !contentEntries) return;

                if (sliderEntries[0].contentRect.height) {
                  this.slideSize.set(sliderEntries[0].contentRect);
                }

                if (contentEntries[0].contentRect.height) {
                  this.contentSize.set(contentEntries[0].contentRect);
                }
              });
            });
          });

          onCleanup(() => resizeSubscription$?.unsubscribe());
        });
      }
    });
  }
}


================================================
FILE: projects/ng-gallery/src/lib/services/scroll-snap-type.spec.ts
================================================
import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { TestComponent } from '../tests/common';
import { ScrollSnapType } from './scroll-snap-type';
import { SmoothScroll } from '../smooth-scroll';
import { HammerSliding } from '../gestures/hammer-sliding.directive';
import { SliderComponent } from '../slider/slider/slider';

describe('Scroll snap type directive', () => {
  let fixture: ComponentFixture<TestComponent>;
  let scrollSnapTypeDirective: ScrollSnapType;
  let smoothScrollDirective: SmoothScroll;
  let hammerSliderDirective: HammerSliding;
  let sliderComponent: SliderComponent;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        NoopAnimationsModule,
        TestComponent
      ],
      providers: [
        { provide: ComponentFixtureAutoDetect, useValue: true }
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(TestComponent);
    fixture.detectChanges();

    const smoothScrollElement: DebugElement = fixture.debugElement.query(By.directive(SmoothScroll));
    smoothScrollDirective = smoothScrollElement.injector.get(SmoothScroll);

    const scrollSnapTypeElement: DebugElement = fixture.debugElement.query(By.directive(ScrollSnapType));
    scrollSnapTypeDirective = scrollSnapTypeElement.injector.get(ScrollSnapType);

    const hammerSliderElement: DebugElement = fixture.debugElement.query(By.directive(HammerSliding));
    hammerSliderDirective = hammerSliderElement.injector.get(HammerSliding);

    const sliderComponentElement: DebugElement = fixture.debugElement.query(By.directive(SliderComponent));
    sliderComponent = sliderComponentElement.componentInstance;
  });

  it('should create [scrollSnapType] directive', () => {
    expect(scrollSnapTypeDirective).toBeTruthy();
  });

  it('should compute "scrollSnapType" to none when gallery is scrolling', () => {
    smoothScrollDirective.scrolling.set(true);
    fixture.detectChanges();

    expect(scrollSnapTypeDirective.scrollSnapType()).toBe('none');
    expect(sliderComponent.nativeElement.style.getPropertyValue('--slider-scroll-snap-type')).toBe('none');
  });

  it('should compute "scrollSnapType" to none when gallery is sliding', () => {
    hammerSliderDirective.sliding.set(true);
    fixture.detectChanges();

    expect(scrollSnapTypeDirective.scrollSnapType()).toBe('none');
    expect(sliderComponent.nativeElement.style.getPropertyValue('--slider-scroll-snap-type')).toBe('none');
  });

  it('should compute "scrollSnapType" to adapter scroll snap type value', () => {
    smoothScrollDirective.scrolling.set(false);
    hammerSliderDirective.sliding.set(false);
    fixture.detectChanges();

    expect(scrollSnapTypeDirective.scrollSnapType()).toBe(sliderComponent.adapter().scrollSnapType);
    expect(sliderComponent.nativeElement.style.getPropertyValue('--slider-scroll-snap-type')).toBe(sliderComponent.adapter().scrollSnapType);
  });

});


================================================
FILE: projects/ng-gallery/src/lib/services/scroll-snap-type.ts
================================================
import { computed, Directive, inject, Signal } from '@angular/core';
import { HammerSliding } from '../gestures/hammer-sliding.directive';
import { SmoothScroll } from '../smooth-scroll';
import { SliderComponent } from '../slider/slider/slider';

@Directive({
  selector: '[scrollSnapType]',
  host: {
    '[style.--slider-scroll-snap-type]': 'scrollSnapType()'
  }
})
export class ScrollSnapType {

  private readonly smoothScroll: SmoothScroll = inject(SmoothScroll, { self: true });

  private readonly hammerSliding: HammerSliding = inject(HammerSliding, { self: true });

  private readonly slider: SliderComponent = inject(SliderComponent, { self: true });

  scrollSnapType: Signal<string> = computed(() => {
    if (this.smoothScroll.scrolling() || this.hammerSliding.sliding()) {
      return 'none';
    }
    return this.slider.adapter().scrollSnapType;
  });
}


================================================
FILE: projects/ng-gallery/src/lib/slider/adapters/base-adapter.ts
================================================
export abstract class SliderAdapter {

  readonly abstract hammerDirection: number;

  readonly abstract scrollSnapType: string;

  abstract get scrollValue(): number;

  abstract get clientSize(): number;

  abstract get isContentLessThanContainer(): boolean;

  abstract getScrollToValue(target: Element, behavior: ScrollBehavior): ScrollToOptions;

  abstract getCentralizerStartSize(): number;

  abstract getCentralizerEndSize(): number;

  abstract getRootMargin(): string;

  abstract getElementRootMargin(viewport: HTMLElement, el: HTMLElement): string;

  abstract getHammerVelocity(e): number;

  abstract getHammerValue(value: number, delta: number, behavior: ScrollBehavior): ScrollToOptions;

  // abstract getDraggingValue(value: number, delta: number, behavior: ScrollBehavior): ScrollToOptions;

  // abstract getDraggingProperty(e: MouseEvent): number;
}


================================================
FILE: projects/ng-gallery/src/lib/slider/adapters/index.ts
================================================
export * from './main-adapters';
export * from './base-adapter';


================================================
FILE: projects/ng-gallery/src/lib/slider/adapters/main-adapter.spec.ts
================================================
import { Component, ElementRef, Signal, viewChild } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import {
  DIRECTION_DOWN,
  DIRECTION_LEFT,
  DIRECTION_RIGHT,
  DIRECTION_UP,
  HorizontalAdapter,
  VerticalAdapter
} from './main-adapters';
import { GalleryConfig } from 'ng-gallery';

@Component({
  selector: 'test-component',
  template: `
    <div class="slider" #slider>
      <div class="content" #content>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
        <div class="item"></div>
      </div>
    </div>
  `,
  styles: [`
    .item {
      height: 50px;
      width: 50px;
    }
  `]
})
class TestComponent {
  slider: Signal<ElementRef<HTMLElement>> = viewChild('slider')
  content: Signal<ElementRef<HTMLElement>> = viewChild('content')
}

describe('HorizontalAdapter', () => {
  let adapter: VerticalAdapter;
  let fixture: ComponentFixture<TestComponent>;
  let component: TestComponent;
  let sliderElement: HTMLElement;
  let contentElement: HTMLElement;
  let config: GalleryConfig;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [TestComponent],
    }).compileComponents();

    fixture = TestBed.createComponent(TestComponent);
    component = fixture.componentInstance;
    fixture.detectChanges(); // Trigger change detection to render the template

    sliderElement = component.slider().nativeElement;
    contentElement = component.content().nativeElement;
    config = {};
    adapter = new VerticalAdapter(sliderElement, config);

    sliderElement.style.width = '500px';
    sliderElement.style.overflowX = 'auto';

    contentElement.style.width = '1000px';

    config = {}; // Or a more specific config if needed
    adapter = new HorizontalAdapter(sliderElement, config);
  });

  it('should create an instance', () => {
    expect(adapter).toBeTruthy();
  });

  it('should have the correct hammerDirection', () => {
    expect(adapter.hammerDirection).toBe(DIRECTION_LEFT | DIRECTION_RIGHT);
  });

  it('should have the correct scrollSnapType', () => {
    expect(adapter.scrollSnapType).toBe('x mandatory');
  });

  it('should return the correct scrollValue', () => {
    sliderElement.scrollLeft = 100;
    expect(adapter.scrollValue).toBe(100);
  });

  it('should return the correct clientSize', () => {
    expect(adapter.clientSize).toBe(500);
  });

  it('should return the correct isContentLessThanContainer value', () => {
    expect(adapter.isContentLessThanContainer).toBe(false); // Content is wider than container in beforeEach setup

    (sliderElement.firstElementChild as HTMLElement).style.width = '400px';
    expect(adapter.isContentLessThanContainer).toBe(true); // Content is now less than container
  });

  it('getScrollToValue should return the correct scroll options', () => {
    const target = document.createElement('div');
    target.style.width = '100px';
    sliderElement.appendChild(target);

    const scrollToOptions = adapter.getScrollToValue(target, 'smooth');
    const expectedPosition = target.offsetLeft - ((adapter.clientSize - target.clientWidth) / 2);
    expect(scrollToOptions).toEqual({
      behavior: 'smooth',
      start: expectedPosition
    });
  });

  it('getRootMargin should return the correct root margin', () => {
    expect(adapter.getRootMargin()).toBe('1000px 0px 1000px 0px');
  });

  it('getElementRootMargin should return the correct element root margin', () => {
    const viewport = document.createElement('div');
    viewport.style.width = '500px';
    const el = document.createElement('div');
    el.style.width = '100px';

    const rootMargin = -1 * ((viewport.clientWidth - el.clientWidth) / 2) + 1;
    expect(adapter.getElementRootMargin(viewport, el)).toBe(`0px ${ rootMargin }px 0px ${ rootMargin }px`);
  });

  it('getCentralizerStartSize should return the correct size when content is less than container', () => {
    (sliderElement.firstElementChild as HTMLElement).style.width = '400px';
    const size = adapter.clientSize - (sliderElement.firstElementChild as HTMLElement).clientWidth;
    expect(adapter.getCentralizerStartSize()).toBe(size / 2);
  });

  it('getCentralizerStartSize should return the correct size when content is greater than or equal to container', () => {
    const expectedSize = (adapter.clientSize / 2) - ((sliderElement.firstElementChild as HTMLElement).firstElementChild?.clientWidth / 2);
    expect(adapter.getCentralizerStartSize()).toBe(expectedSize);
  });

  it('getCentralizerEndSize should return the correct size when content is less than container', () => {
    (sliderElement.firstElementChild as HTMLElement).style.width = '400px';
    const size = adapter.clientSize - (sliderElement.firstElementChild as HTMLElement).clientWidth;
    expect(adapter.getCentralizerEndSize()).toBe(size / 2);
  });

  it('getCentralizerEndSize should return the correct size when content is greater than or equal to container', () => {
    const expectedSize = (adapter.clientSize / 2) - ((sliderElement.firstElementChild as HTMLElement).lastElementChild?.clientWidth / 2);
    expect(adapter.getCentralizerEndSize()).toBe(expectedSize);
  });

  it('getHammerVelocity should return the correct velocity', () => {
    const e = { velocityX: 2.5 };
    expect(adapter.getHammerVelocity(e)).toBe(e.velocityX);
  });

  it('getHammerValue should return the correct scroll options', () => {
    const value = 100;
    const e = { deltaX: 50 };
    const scrollToOptions = adapter.getHammerValue(value, e, 'smooth');
    expect(scrollToOptions).toEqual({
      behavior: 'smooth',
      left: value - e.deltaX
    });
  });
});

describe('VerticalAdapter', () => {
  let adapter: VerticalAdapter;
  let fixture: ComponentFixture<TestComponent>;
  let component: TestComponent;
  let sliderElement: HTMLElement;
  let contentElement: HTMLElement;
  let config: GalleryConfig;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [TestComponent],
    }).compileComponents();

    fixture = TestBed.createComponent(TestComponent);
    component = fixture.componentInstance;
    fixture.detectChanges(); // Trigger change detection to render the template

    sliderElement = component.slider().nativeElement;
    contentElement = component.content().nativeElement;
    config = {};
    adapter = new VerticalAdapter(sliderElement, config);

    sliderElement.style.height = '500px';
    sliderElement.style.overflowX = 'auto';

    contentElement.style.height = '1000px';

    config = {};
    adapter = new VerticalAdapter(sliderElement, config);
  });

  it('should create an instance', () => {
    expect(adapter).toBeTruthy();
  });

  it('should have the correct hammerDirection', () => {
    expect(adapter.hammerDirection).toBe(DIRECTION_UP | DIRECTION_DOWN);
  });

  it('should have the correct scrollSnapType', () => {
    expect(adapter.scrollSnapType).toBe('y mandatory');
  });

  it('should return the correct scrollValue', () => {
    sliderElement.scrollTop = 100;
    expect(adapter.scrollValue).toBe(100);
  });

  it('should return the correct clientSize', () => {
    expect(adapter.clientSize).toBe(500);
  });

  it('should return the correct isContentLessThanContainer value', () => {
    expect(adapter.isContentLessThanContainer).toBe(false); // Content is taller than container in beforeEach setup

    (sliderElement.firstElementChild as HTMLElement).style.height = '400px';
    expect(adapter.isContentLessThanContainer).toBe(true); // Content is now less than container
  });

  it('getScrollToValue should return the correct scroll options', () => {
    const target = document.createElement('div');
    target.style.height = '100px';
    sliderElement.appendChild(target);

    const scrollToOptions = adapter.getScrollToValue(target, 'smooth');
    const expectedPosition = target.offsetTop - ((adapter.clientSize - target.clientHeight) / 2);
    expect(scrollToOptions).toEqual({
      behavior: 'smooth',
      top: expectedPosition
    });
  });

  it('getRootMargin should return the correct root margin', () => {
    expect(adapter.getRootMargin()).toBe('0px 1000px 0px 1000px');
  });

  it('getElementRootMargin should return the correct element root margin', () => {
    const viewport = document.createElement('div');
    viewport.style.height = '500px';
    const el = document.createElement('div');
    el.style.height = '100px';

    const rootMargin = -1 * ((viewport.clientHeight - el.clientHeight) / 2) + 1;
    expect(adapter.getElementRootMargin(viewport, el)).toBe(`${ rootMargin }px 0px ${ rootMargin }px 0px`);
  });

  it('getCentralizerStartSize should return the correct size when content is less than container', ()
Download .txt
gitextract_ao06cvmc/

├── .editorconfig
├── .eslintrc.json
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── feature_request.md
│   │   └── question.md
│   └── workflows/
│       ├── integrate.yml
│       └── netlify.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── angular.json
├── package.json
├── projects/
│   ├── ng-gallery/
│   │   ├── .eslintrc.json
│   │   ├── .storybook/
│   │   │   ├── colors.mdx
│   │   │   ├── main.ts
│   │   │   ├── manager.js
│   │   │   ├── preview.ts
│   │   │   ├── theme.js
│   │   │   ├── tsconfig.json
│   │   │   └── typings.d.ts
│   │   ├── README.md
│   │   ├── karma.conf.js
│   │   ├── lightbox/
│   │   │   ├── ng-package.json
│   │   │   └── src/
│   │   │       ├── gallerize.directive.ts
│   │   │       ├── lightbox.animation.ts
│   │   │       ├── lightbox.component.scss
│   │   │       ├── lightbox.component.ts
│   │   │       ├── lightbox.default.ts
│   │   │       ├── lightbox.directive.ts
│   │   │       ├── lightbox.model.ts
│   │   │       ├── lightbox.module.ts
│   │   │       ├── lightbox.service.ts
│   │   │       └── public_api.ts
│   │   ├── ng-package.json
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── lib/
│   │   │   │   ├── auto-height/
│   │   │   │   │   ├── auto-height.spec.ts
│   │   │   │   │   └── auto-height.ts
│   │   │   │   ├── autoplay/
│   │   │   │   │   └── autoplay.directive.ts
│   │   │   │   ├── bullets/
│   │   │   │   │   ├── gallery-bullets.component.ts
│   │   │   │   │   ├── gallery-bullets.scss
│   │   │   │   │   └── gallery-bullets.spec.ts
│   │   │   │   ├── core/
│   │   │   │   │   ├── gallery.component.ts
│   │   │   │   │   ├── gallery.scss
│   │   │   │   │   └── gallery.spec.ts
│   │   │   │   ├── counter/
│   │   │   │   │   ├── gallery-counter.component.ts
│   │   │   │   │   ├── gallery-counter.scss
│   │   │   │   │   └── gallery-counter.spec.ts
│   │   │   │   ├── debug/
│   │   │   │   │   └── debug.scss
│   │   │   │   ├── directives/
│   │   │   │   │   ├── gallery-box-def.directive.spec.ts
│   │   │   │   │   ├── gallery-box-def.directive.ts
│   │   │   │   │   ├── gallery-item-def.directive.spec.ts
│   │   │   │   │   └── gallery-item-def.directive.ts
│   │   │   │   ├── gallery.module.ts
│   │   │   │   ├── gestures/
│   │   │   │   │   ├── hammer-slider.spec.ts
│   │   │   │   │   ├── hammer-sliding.directive.ts
│   │   │   │   │   └── mouse-sliding.directive.ts
│   │   │   │   ├── models/
│   │   │   │   │   ├── config.model.ts
│   │   │   │   │   ├── constants.ts
│   │   │   │   │   ├── item.model.ts
│   │   │   │   │   ├── slider.model.ts
│   │   │   │   │   └── styles.model.ts
│   │   │   │   ├── nav/
│   │   │   │   │   ├── gallery-nav.component.ts
│   │   │   │   │   ├── gallery-nav.scss
│   │   │   │   │   └── gallery-nav.spec.ts
│   │   │   │   ├── observers/
│   │   │   │   │   ├── intersection-directive.spec.ts
│   │   │   │   │   ├── intersection-observer.ts
│   │   │   │   │   └── intersection-sensor.directive.ts
│   │   │   │   ├── services/
│   │   │   │   │   ├── gallery-ref.ts
│   │   │   │   │   ├── hammer.ts
│   │   │   │   │   ├── resize-directive.spec.ts
│   │   │   │   │   ├── resize-sensor.ts
│   │   │   │   │   ├── scroll-snap-type.spec.ts
│   │   │   │   │   └── scroll-snap-type.ts
│   │   │   │   ├── slider/
│   │   │   │   │   ├── adapters/
│   │   │   │   │   │   ├── base-adapter.ts
│   │   │   │   │   │   ├── index.ts
│   │   │   │   │   │   ├── main-adapter.spec.ts
│   │   │   │   │   │   └── main-adapters.ts
│   │   │   │   │   ├── gallery-slider.component.ts
│   │   │   │   │   ├── gallery-slider.scss
│   │   │   │   │   ├── slider/
│   │   │   │   │   │   ├── slider.spec.ts
│   │   │   │   │   │   └── slider.ts
│   │   │   │   │   └── slider-item/
│   │   │   │   │       ├── slider-item.scss
│   │   │   │   │       ├── slider-item.spec.ts
│   │   │   │   │       └── slider-item.ts
│   │   │   │   ├── smooth-scroll/
│   │   │   │   │   ├── bezier-easing.spec.ts
│   │   │   │   │   ├── bezier-easing.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   ├── smooth-scroll.directive.ts
│   │   │   │   │   ├── smooth-scroll.model.ts
│   │   │   │   │   └── smooth-scroll.spec.ts
│   │   │   │   ├── templates/
│   │   │   │   │   ├── gallery-iframe.component.ts
│   │   │   │   │   ├── gallery-image.component.spec.ts
│   │   │   │   │   ├── gallery-image.component.ts
│   │   │   │   │   ├── gallery-image.scss
│   │   │   │   │   ├── gallery-video.component.ts
│   │   │   │   │   ├── items.model.ts
│   │   │   │   │   └── svg-assets.ts
│   │   │   │   ├── tests/
│   │   │   │   │   ├── common.ts
│   │   │   │   │   └── test-images.ts
│   │   │   │   ├── thumbs/
│   │   │   │   │   ├── gallery-thumbs.component.ts
│   │   │   │   │   ├── gallery-thumbs.scss
│   │   │   │   │   └── gallery-thumbs.spec.ts
│   │   │   │   └── utils/
│   │   │   │       ├── gallery.default.ts
│   │   │   │       ├── img-manager.spec.ts
│   │   │   │       ├── img-manager.ts
│   │   │   │       ├── img-recognizer.spec.ts
│   │   │   │       ├── img-recognizer.ts
│   │   │   │       ├── item.class.spec.ts
│   │   │   │       └── item.class.ts
│   │   │   ├── public-api.ts
│   │   │   ├── stories/
│   │   │   │   ├── .eslintrc.json
│   │   │   │   ├── GettingStarted.mdx
│   │   │   │   ├── LoadItems.mdx
│   │   │   │   ├── Responsiveness.mdx
│   │   │   │   ├── basic/
│   │   │   │   │   ├── Bullets.mdx
│   │   │   │   │   ├── Counter.mdx
│   │   │   │   │   ├── Gallery.stories.ts
│   │   │   │   │   ├── Navigation.mdx
│   │   │   │   │   ├── Player.mdx
│   │   │   │   │   ├── Slider.mdx
│   │   │   │   │   └── Thumbnails.mdx
│   │   │   │   ├── custom-templates/
│   │   │   │   │   ├── CustomTemplates.stories.ts
│   │   │   │   │   ├── CustomTemplatesUsage.mdx
│   │   │   │   │   └── custom-template.component.ts
│   │   │   │   ├── lightbox/
│   │   │   │   │   ├── CustomTemplates.mdx
│   │   │   │   │   ├── Gallerize.mdx
│   │   │   │   │   ├── GettingStarted.mdx
│   │   │   │   │   ├── Lightbox.mdx
│   │   │   │   │   ├── Lightbox.stories.ts
│   │   │   │   │   └── lightbox-example.ts
│   │   │   │   └── pixabay/
│   │   │   │       ├── pixabay.model.ts
│   │   │   │       └── pixabay.service.ts
│   │   │   └── test.ts
│   │   ├── tsconfig.lib.json
│   │   ├── tsconfig.lib.prod.json
│   │   └── tsconfig.spec.json
│   └── ng-gallery-demo/
│       ├── public/
│       │   └── icons/
│       │       ├── browserconfig.xml
│       │       └── site.webmanifest.json
│       ├── src/
│       │   ├── app/
│       │   │   ├── app-routing.animations.ts
│       │   │   ├── app.component.html
│       │   │   ├── app.component.scss
│       │   │   ├── app.component.ts
│       │   │   ├── app.config.server.ts
│       │   │   ├── app.config.ts
│       │   │   ├── app.routes.ts
│       │   │   ├── pages/
│       │   │   │   ├── documentation/
│       │   │   │   │   ├── doc-core/
│       │   │   │   │   │   ├── doc-core.component.html
│       │   │   │   │   │   ├── doc-core.component.scss
│       │   │   │   │   │   └── doc-core.component.ts
│       │   │   │   │   ├── doc-lightbox/
│       │   │   │   │   │   ├── doc-lightbox.component.html
│       │   │   │   │   │   ├── doc-lightbox.component.scss
│       │   │   │   │   │   └── doc-lightbox.component.ts
│       │   │   │   │   ├── documentation.component.html
│       │   │   │   │   ├── documentation.component.scss
│       │   │   │   │   ├── documentation.component.ts
│       │   │   │   │   └── routes.ts
│       │   │   │   ├── examples/
│       │   │   │   │   └── basic-example/
│       │   │   │   │       └── basic-example.ts
│       │   │   │   ├── gallerize-example/
│       │   │   │   │   ├── gallerize-example.component.html
│       │   │   │   │   ├── gallerize-example.component.scss
│       │   │   │   │   └── gallerize-example.component.ts
│       │   │   │   ├── gallery-example/
│       │   │   │   │   ├── gallery-example.component.html
│       │   │   │   │   ├── gallery-example.component.scss
│       │   │   │   │   └── gallery-example.component.ts
│       │   │   │   ├── home/
│       │   │   │   │   ├── home.component.html
│       │   │   │   │   ├── home.component.scss
│       │   │   │   │   └── home.component.ts
│       │   │   │   ├── lab/
│       │   │   │   │   ├── lab.component.html
│       │   │   │   │   ├── lab.component.scss
│       │   │   │   │   └── lab.component.ts
│       │   │   │   ├── lightbox-example/
│       │   │   │   │   ├── lightbox-example.component.html
│       │   │   │   │   ├── lightbox-example.component.scss
│       │   │   │   │   └── lightbox-example.component.ts
│       │   │   │   ├── not-found/
│       │   │   │   │   ├── not-found.component.html
│       │   │   │   │   ├── not-found.component.scss
│       │   │   │   │   └── not-found.component.ts
│       │   │   │   └── templates-example/
│       │   │   │       ├── slide-text.animation.ts
│       │   │   │       ├── templates-example.component.html
│       │   │   │       ├── templates-example.component.scss
│       │   │   │       └── templates-example.component.ts
│       │   │   ├── service/
│       │   │   │   ├── pixabay.model.ts
│       │   │   │   └── pixabay.service.ts
│       │   │   └── shared/
│       │   │       ├── badges/
│       │   │       │   ├── badges.component.html
│       │   │       │   └── badges.component.ts
│       │   │       ├── footer/
│       │   │       │   ├── footer.component.html
│       │   │       │   ├── footer.component.scss
│       │   │       │   └── footer.component.ts
│       │   │       ├── gallery-mock-dialog.ts
│       │   │       ├── hl-code/
│       │   │       │   ├── hl-code.component.html
│       │   │       │   ├── hl-code.component.scss
│       │   │       │   └── hl-code.component.ts
│       │   │       ├── menu/
│       │   │       │   ├── menu.component.html
│       │   │       │   ├── menu.component.scss
│       │   │       │   └── menu.component.ts
│       │   │       ├── note/
│       │   │       │   ├── note.component.scss
│       │   │       │   └── note.component.ts
│       │   │       ├── pipes/
│       │   │       │   └── keys.pipe.ts
│       │   │       └── section-title/
│       │   │           ├── section-title.component.scss
│       │   │           └── section-title.component.ts
│       │   ├── index.html
│       │   ├── main.server.ts
│       │   ├── main.ts
│       │   ├── mixin.scss
│       │   ├── server.ts
│       │   ├── styles.scss
│       │   └── theme.scss
│       ├── tsconfig.app.json
│       └── tsconfig.spec.json
└── tsconfig.json
Download .txt
SYMBOL INDEX (248 symbols across 75 files)

FILE: projects/ng-gallery-demo/src/app/app.component.ts
  class AppComponent (line 38) | class AppComponent implements OnInit {
    method constructor (line 43) | constructor(private router: Router, private matIconRegistry: MatIconRe...
    method ngOnInit (line 52) | ngOnInit() {
    method getState (line 62) | getState(outlet) {

FILE: projects/ng-gallery-demo/src/app/pages/documentation/doc-core/doc-core.component.ts
  class DocCoreComponent (line 15) | class DocCoreComponent {

FILE: projects/ng-gallery-demo/src/app/pages/documentation/doc-lightbox/doc-lightbox.component.ts
  class DocLightboxComponent (line 16) | class DocLightboxComponent {

FILE: projects/ng-gallery-demo/src/app/pages/documentation/documentation.component.ts
  class DocumentationComponent (line 23) | class DocumentationComponent implements OnInit {
    method constructor (line 25) | constructor(private _title: Title) {
    method ngOnInit (line 28) | ngOnInit() {

FILE: projects/ng-gallery-demo/src/app/pages/documentation/routes.ts
  constant DOCUMENTATION_ROUTES (line 4) | const DOCUMENTATION_ROUTES: Routes = [

FILE: projects/ng-gallery-demo/src/app/pages/examples/basic-example/basic-example.ts
  class BasicExampleComponent (line 16) | class BasicExampleComponent {

FILE: projects/ng-gallery-demo/src/app/pages/gallerize-example/gallerize-example.component.ts
  class GallerizeExampleComponent (line 24) | class GallerizeExampleComponent implements OnInit {
    method constructor (line 30) | constructor(private _pixabay: Pixabay, private _title: Title) {
    method ngOnInit (line 52) | ngOnInit() {

FILE: projects/ng-gallery-demo/src/app/pages/gallery-example/gallery-example.component.ts
  class GalleryExampleComponent (line 39) | class GalleryExampleComponent implements OnInit {
    method constructor (line 45) | constructor(pixabay: Pixabay, private _title: Title) {
    method ngOnInit (line 65) | ngOnInit() {

FILE: projects/ng-gallery-demo/src/app/pages/home/home.component.ts
  class HomeComponent (line 41) | class HomeComponent implements OnInit {
    method constructor (line 46) | constructor(pixabay: Pixabay, private _title: Title) {
    method ngOnInit (line 64) | ngOnInit() {

FILE: projects/ng-gallery-demo/src/app/pages/lab/lab.component.ts
  class LabComponent (line 64) | class LabComponent implements OnInit {
    method ngOnInit (line 136) | ngOnInit(): void {
    method restart (line 140) | restart(): void {
    method onPlayer (line 145) | onPlayer(e): void {
    method onItemClick (line 149) | onItemClick(e): void {
    method onThumbClick (line 153) | onThumbClick(e): void {
    method onIndexChange (line 157) | onIndexChange(e): void {
    method updateEvent (line 161) | private updateEvent(eventState: WritableSignal<any>, e?: any): void {

FILE: projects/ng-gallery-demo/src/app/pages/lightbox-example/lightbox-example.component.ts
  class LightboxExampleComponent (line 53) | class LightboxExampleComponent implements OnInit {
    method ngOnInit (line 62) | ngOnInit(): void {

FILE: projects/ng-gallery-demo/src/app/pages/not-found/not-found.component.ts
  class NotFoundComponent (line 15) | class NotFoundComponent implements OnInit {
    method constructor (line 17) | constructor(private _title: Title) {
    method ngOnInit (line 20) | ngOnInit() {

FILE: projects/ng-gallery-demo/src/app/pages/templates-example/templates-example.component.ts
  class TemplatesExampleComponent (line 50) | class TemplatesExampleComponent {
    method constructor (line 61) | constructor(private _title: Title) {

FILE: projects/ng-gallery-demo/src/app/service/pixabay.model.ts
  type PixabayModel (line 1) | interface PixabayModel {
  type Hit (line 7) | interface Hit {
  type PixabayHDModel (line 30) | interface PixabayHDModel {
  type Hit2 (line 36) | interface Hit2 {

FILE: projects/ng-gallery-demo/src/app/service/pixabay.service.ts
  class Pixabay (line 12) | class Pixabay {
    method getHDImages (line 18) | getHDImages(key: string): Observable<GalleryItemData[]> {

FILE: projects/ng-gallery-demo/src/app/shared/badges/badges.component.ts
  class BadgesComponent (line 21) | class BadgesComponent {

FILE: projects/ng-gallery-demo/src/app/shared/footer/footer.component.ts
  class FooterComponent (line 13) | class FooterComponent {

FILE: projects/ng-gallery-demo/src/app/shared/gallery-mock-dialog.ts
  class GalleryMockDialog (line 32) | class GalleryMockDialog {
    method constructor (line 33) | constructor(public dialogRef: MatDialogRef<GalleryMockDialog>, @Inject...

FILE: projects/ng-gallery-demo/src/app/shared/hl-code/hl-code.component.ts
  class HlCodeComponent (line 17) | class HlCodeComponent {

FILE: projects/ng-gallery-demo/src/app/shared/menu/menu.component.ts
  class MenuComponent (line 13) | class MenuComponent {

FILE: projects/ng-gallery-demo/src/app/shared/note/note.component.ts
  class NoteComponent (line 19) | class NoteComponent {

FILE: projects/ng-gallery-demo/src/app/shared/pipes/keys.pipe.ts
  class KeysPipe (line 7) | class KeysPipe implements PipeTransform {
    method transform (line 8) | transform(value, args: string[]): any {

FILE: projects/ng-gallery-demo/src/app/shared/section-title/section-title.component.ts
  class SectionTitleComponent (line 20) | class SectionTitleComponent {

FILE: projects/ng-gallery/lightbox/src/gallerize.directive.ts
  type GallerizeMode (line 25) | const enum GallerizeMode {
  class GallerizeDirective (line 34) | class GallerizeDirective implements OnInit, OnDestroy {
    method constructor (line 64) | constructor(private _zone: NgZone,
    method ngOnInit (line 75) | ngOnInit(): void {
    method ngOnDestroy (line 90) | ngOnDestroy(): void {
    method galleryMode (line 104) | private galleryMode(galleryRef: GalleryRef): void {
    method detectorMode (line 112) | private detectorMode(galleryRef: GalleryRef): void {

FILE: projects/ng-gallery/lightbox/src/lightbox.component.ts
  class LightboxComponent (line 16) | class LightboxComponent {
    method showModal (line 22) | showModal(i: number): void {
    method close (line 27) | close(): void {

FILE: projects/ng-gallery/lightbox/src/lightbox.model.ts
  constant LIGHTBOX_CONFIG (line 4) | const LIGHTBOX_CONFIG: InjectionToken<LightboxConfig> = new InjectionTok...
  type LightboxConfig (line 9) | interface LightboxConfig {
  function provideLightboxOptions (line 23) | function provideLightboxOptions(options: LightboxConfig): Provider {

FILE: projects/ng-gallery/lightbox/src/lightbox.module.ts
  class LightboxModule (line 18) | class LightboxModule {

FILE: projects/ng-gallery/lightbox/src/lightbox.service.ts
  class Lightbox (line 15) | class Lightbox {
    method setConfig (line 39) | setConfig(config: LightboxConfig) {
    method open (line 49) | open(i = 0, id = 'lightbox', config?: LightboxConfig) {
    method close (line 101) | close() {

FILE: projects/ng-gallery/src/lib/bullets/gallery-bullets.component.ts
  class GalleryBulletsComponent (line 34) | class GalleryBulletsComponent {

FILE: projects/ng-gallery/src/lib/bullets/gallery-bullets.spec.ts
  class TestComponent (line 27) | class TestComponent {

FILE: projects/ng-gallery/src/lib/core/gallery.component.ts
  class GalleryComponent (line 68) | class GalleryComponent {
    method constructor (line 245) | constructor() {
    method next (line 260) | next(behavior?: ScrollBehavior, loop?: boolean): void {
    method prev (line 267) | prev(behavior?: ScrollBehavior, loop?: boolean): void {
    method set (line 274) | set(i: number, behavior?: ScrollBehavior): void {
    method reset (line 281) | reset(): void {
    method play (line 288) | play(interval?: number): void {
    method stop (line 295) | stop(): void {

FILE: projects/ng-gallery/src/lib/counter/gallery-counter.component.ts
  class GalleryCounterComponent (line 24) | class GalleryCounterComponent {

FILE: projects/ng-gallery/src/lib/counter/gallery-counter.spec.ts
  class TestComponent (line 29) | class TestComponent {

FILE: projects/ng-gallery/src/lib/directives/gallery-box-def.directive.spec.ts
  class TestComponent (line 6) | @Component({

FILE: projects/ng-gallery/src/lib/directives/gallery-box-def.directive.ts
  class GalleryBoxDef (line 8) | class GalleryBoxDef {
    method ngTemplateContextGuard (line 14) | static ngTemplateContextGuard(
  type GalleryStateContext (line 22) | interface GalleryStateContext {

FILE: projects/ng-gallery/src/lib/directives/gallery-item-def.directive.spec.ts
  class TestComponent (line 7) | @Component({

FILE: projects/ng-gallery/src/lib/directives/gallery-item-def.directive.ts
  class GalleryItemDef (line 7) | class GalleryItemDef {
    method ngTemplateContextGuard (line 12) | static ngTemplateContextGuard(
  type GalleryItemContext (line 20) | interface GalleryItemContext<T> {

FILE: projects/ng-gallery/src/lib/gallery.module.ts
  class GalleryModule (line 18) | class GalleryModule {

FILE: projects/ng-gallery/src/lib/gestures/hammer-sliding.directive.ts
  class HammerSliding (line 36) | class HammerSliding {
    method constructor (line 60) | constructor() {
    method getIndexOnMouseUp (line 156) | private getIndexOnMouseUp(e: any, adapter: SliderAdapter): number {

FILE: projects/ng-gallery/src/lib/models/config.model.ts
  constant GALLERY_CONFIG (line 6) | const GALLERY_CONFIG: InjectionToken<GalleryConfig> = new InjectionToken...
  function provideGalleryOptions (line 11) | function provideGalleryOptions(options: GalleryConfig): Provider {
  type ImageSize (line 18) | type ImageSize = 'contain' | 'cover';
  type Orientation (line 20) | type Orientation = 'horizontal' | 'vertical';
  type ThumbsPosition (line 22) | type ThumbsPosition = 'top' | 'left' | 'right' | 'bottom';
  type HorizontalPosition (line 24) | type HorizontalPosition = 'top' | 'bottom';
  type ThumbConfig (line 26) | interface ThumbConfig {
  type NavConfig (line 31) | interface NavConfig {
  type PlayerConfig (line 35) | interface PlayerConfig {
  type SliderConfig (line 40) | interface SliderConfig {
  type GalleryConfig (line 54) | type GalleryConfig = SliderConfig

FILE: projects/ng-gallery/src/lib/models/constants.ts
  type IMAGE_SIZE (line 1) | enum IMAGE_SIZE {
  type LOADING_STRATEGY (line 6) | enum LOADING_STRATEGY {
  type LOADING_ATTR (line 12) | enum LOADING_ATTR {
  type THUMB_POSITION (line 17) | enum THUMB_POSITION {
  type BulletsPosition (line 24) | enum BulletsPosition {
  type CounterPosition (line 29) | enum CounterPosition {
  type ORIENTATION (line 34) | enum ORIENTATION {
  type ITEM_TYPE (line 39) | enum ITEM_TYPE {
  type GalleryItemType (line 47) | type GalleryItemType = ITEM_TYPE | string;

FILE: projects/ng-gallery/src/lib/models/item.model.ts
  type ItemState (line 1) | type ItemState = 'success' | 'loading' | 'failed';

FILE: projects/ng-gallery/src/lib/models/slider.model.ts
  type SliderState (line 1) | interface SliderState {
  type WorkerState (line 6) | interface WorkerState {
  type IndexChange (line 11) | interface IndexChange {

FILE: projects/ng-gallery/src/lib/nav/gallery-nav.component.ts
  class GalleryNavComponent (line 30) | class GalleryNavComponent {

FILE: projects/ng-gallery/src/lib/nav/gallery-nav.spec.ts
  class TestComponent (line 28) | class TestComponent {

FILE: projects/ng-gallery/src/lib/observers/intersection-observer.ts
  function createIntersectionObserver (line 3) | function createIntersectionObserver(options: IntersectionObserverInit, e...

FILE: projects/ng-gallery/src/lib/observers/intersection-sensor.directive.ts
  class IntersectionSensor (line 28) | class IntersectionSensor {
    method constructor (line 46) | constructor() {

FILE: projects/ng-gallery/src/lib/services/gallery-ref.ts
  class GalleryRef (line 9) | class GalleryRef {
    method setConfig (line 51) | setConfig(newConfig: GalleryConfig): void {
    method add (line 60) | add(newItem: GalleryItemData, active?: boolean): void {
    method remove (line 73) | remove(i: number): void {
    method load (line 87) | load(items: GalleryItemData[]): void {
    method set (line 97) | set(i: number, behavior?: ScrollBehavior): void {
    method next (line 112) | next(behavior?: ScrollBehavior, loop: boolean = true): void {
    method prev (line 123) | prev(behavior?: ScrollBehavior, loop: boolean = true): void {
    method play (line 134) | play(interval?: number): void {
    method stop (line 146) | stop(): void {
    method reset (line 153) | reset(): void {

FILE: projects/ng-gallery/src/lib/services/hammer.ts
  type HammerInstance (line 4) | interface HammerInstance {
  class CustomHammerConfig (line 13) | class CustomHammerConfig extends HammerGestureConfig {

FILE: projects/ng-gallery/src/lib/services/resize-sensor.ts
  class ResizeSensor (line 29) | class ResizeSensor {
    method constructor (line 59) | constructor() {

FILE: projects/ng-gallery/src/lib/services/scroll-snap-type.ts
  class ScrollSnapType (line 12) | class ScrollSnapType {

FILE: projects/ng-gallery/src/lib/slider/adapters/main-adapter.spec.ts
  class TestComponent (line 13) | @Component({

FILE: projects/ng-gallery/src/lib/slider/adapters/main-adapters.ts
  constant DIRECTION_LEFT (line 8) | const DIRECTION_LEFT: number = 2;
  constant DIRECTION_RIGHT (line 9) | const DIRECTION_RIGHT: number = 4;
  constant DIRECTION_UP (line 10) | const DIRECTION_UP: number = 8;
  constant DIRECTION_DOWN (line 11) | const DIRECTION_DOWN: number = 16;
  class HorizontalAdapter (line 13) | class HorizontalAdapter implements SliderAdapter {
    method scrollValue (line 19) | get scrollValue(): number {
    method clientSize (line 23) | get clientSize(): number {
    method isContentLessThanContainer (line 27) | get isContentLessThanContainer(): boolean {
    method constructor (line 31) | constructor(public slider: HTMLElement, public config: GalleryConfig) {
    method getScrollToValue (line 34) | getScrollToValue(target: HTMLElement, behavior: ScrollBehavior): Smoot...
    method getRootMargin (line 42) | getRootMargin(): string {
    method getElementRootMargin (line 47) | getElementRootMargin(viewport: HTMLElement, el: HTMLElement): string {
    method getCentralizerStartSize (line 52) | getCentralizerStartSize(): number {
    method getCentralizerEndSize (line 60) | getCentralizerEndSize(): number {
    method getHammerVelocity (line 68) | getHammerVelocity(e: any): number {
    method getHammerValue (line 72) | getHammerValue(value: number, e: any, behavior: ScrollBehavior): Scrol...
  class VerticalAdapter (line 91) | class VerticalAdapter implements SliderAdapter {
    method scrollValue (line 97) | get scrollValue(): number {
    method clientSize (line 101) | get clientSize(): number {
    method isContentLessThanContainer (line 105) | get isContentLessThanContainer(): boolean {
    method constructor (line 109) | constructor(public slider: HTMLElement, public config: GalleryConfig) {
    method getScrollToValue (line 112) | getScrollToValue(target: HTMLElement, behavior: ScrollBehavior): Smoot...
    method getRootMargin (line 120) | getRootMargin(): string {
    method getElementRootMargin (line 124) | getElementRootMargin(viewport: HTMLElement, el: HTMLElement): string {
    method getCentralizerStartSize (line 129) | getCentralizerStartSize(): number {
    method getCentralizerEndSize (line 137) | getCentralizerEndSize(): number {
    method getHammerVelocity (line 145) | getHammerVelocity(e: any): number {
    method getHammerValue (line 149) | getHammerValue(value: number, e: any, behavior: ScrollBehavior): Scrol...

FILE: projects/ng-gallery/src/lib/slider/gallery-slider.component.ts
  class GallerySliderComponent (line 68) | class GallerySliderComponent {

FILE: projects/ng-gallery/src/lib/slider/slider-item/slider-item.ts
  class SliderItem (line 41) | class SliderItem implements AfterViewInit {
    method ngAfterViewInit (line 88) | ngAfterViewInit(): void {

FILE: projects/ng-gallery/src/lib/slider/slider/slider.ts
  class SliderComponent (line 32) | class SliderComponent {
    method visibleEntries (line 57) | get visibleEntries(): WritableSignal<Record<number, IntersectionObserv...

FILE: projects/ng-gallery/src/lib/smooth-scroll/bezier-easing.ts
  constant NEWTON_ITERATIONS (line 8) | const NEWTON_ITERATIONS: number = 4;
  constant NEWTON_MIN_SLOPE (line 9) | const NEWTON_MIN_SLOPE: number = 0.001;
  constant SUBDIVISION_PRECISION (line 10) | const SUBDIVISION_PRECISION: number = 0.0000001;
  constant SUBDIVISION_MAX_ITERATIONS (line 11) | const SUBDIVISION_MAX_ITERATIONS: number = 10;
  function A (line 16) | function A(aA1: number, aA2: number): number {
  function B (line 20) | function B(aA1: number, aA2: number): number {
  function C (line 24) | function C(aA1: number): number {
  function calcBezier (line 29) | function calcBezier(aT: number, aA1: number, aA2: number): number {
  function getSlope (line 34) | function getSlope(aT: number, aA1: number, aA2: number): number {
  function binarySubdivide (line 38) | function binarySubdivide(aX: number, aA: number, aB: number, mX1: number...
  function newtonRaphsonIterate (line 52) | function newtonRaphsonIterate(aX: number, aGuessT: number, mX1: number, ...
  function LinearEasing (line 64) | function LinearEasing(x: number): number {
  function bezier (line 68) | function bezier(mX1: number, mY1: number, mX2: number, mY2: number) {

FILE: projects/ng-gallery/src/lib/smooth-scroll/smooth-scroll.directive.ts
  class SmoothScroll (line 46) | class SmoothScroll {
    method _now (line 69) | private get _now(): () => number {
    method constructor (line 79) | constructor() {
    method scrollElement (line 143) | scrollElement(x: number, y: number): void {
    method resetElement (line 148) | private resetElement(): void {
    method _isFinished (line 157) | private _isFinished(context: SmoothScrollStep): boolean {
    method _interrupted (line 168) | private _interrupted(): Observable<Event | void> {
    method _step (line 179) | private _step(context: SmoothScrollStep): Observable<SmoothScrollStep> {
    method _applyScrollToOptions (line 201) | private _applyScrollToOptions(options: SmoothScrollToOptions): void {
    method _prepareParams (line 216) | private _prepareParams(params: SmoothScrollOptions): SmoothScrollToOpt...
    method scrollTo (line 263) | scrollTo(params: SmoothScrollOptions): void {

FILE: projects/ng-gallery/src/lib/smooth-scroll/smooth-scroll.model.ts
  type SmoothScrollOptions (line 3) | type SmoothScrollOptions = Partial<Pick<_XAxis, keyof _XAxis> & Pick<_YA...
  type SmoothScrollToOptions (line 7) | type SmoothScrollToOptions = SmoothScrollOptions & {
  type SmoothScrollStep (line 12) | interface SmoothScrollStep {
  type BezierEasingOptions (line 25) | interface BezierEasingOptions {

FILE: projects/ng-gallery/src/lib/templates/gallery-image.component.ts
  class GalleryImageComponent (line 87) | class GalleryImageComponent {

FILE: projects/ng-gallery/src/lib/templates/items.model.ts
  type GalleryItemModel (line 1) | type GalleryItemModel = {
  type ImageItemData (line 8) | type ImageItemData = GalleryItemModel & {
  type IframeItemData (line 12) | type IframeItemData = GalleryItemModel & {
  type YoutubeItemData (line 16) | type YoutubeItemData = IframeItemData & {
  type VimeoItemData (line 20) | type VimeoItemData = IframeItemData & {
  type VideoItemData (line 24) | type VideoItemData = GalleryItemModel & {
  type GalleryItemData (line 37) | type GalleryItemData = GalleryItemModel;

FILE: projects/ng-gallery/src/lib/tests/common.ts
  class TestComponent (line 18) | class TestComponent {
  function afterTimeout (line 39) | async function afterTimeout(timeout: number): Promise<void> {
  function getObservableFromContext (line 44) | function getObservableFromContext<T = unknown>(signal: Signal<T>): Obser...

FILE: projects/ng-gallery/src/lib/thumbs/gallery-thumbs.component.ts
  class GalleryThumbsComponent (line 74) | class GalleryThumbsComponent {

FILE: projects/ng-gallery/src/lib/thumbs/gallery-thumbs.spec.ts
  class TestComponent (line 34) | class TestComponent {

FILE: projects/ng-gallery/src/lib/utils/img-manager.ts
  type ImageRegistry (line 7) | interface ImageRegistry {
  class ImgManager (line 17) | class ImgManager {
    method getActiveItem (line 27) | getActiveItem(): Observable<HTMLImageElement> {
    method addItem (line 42) | addItem(index: number, payload: ImageRegistry): void {
    method deleteItem (line 47) | deleteItem(index: number): void {

FILE: projects/ng-gallery/src/lib/utils/img-recognizer.spec.ts
  class TestComponent (line 7) | @Component({

FILE: projects/ng-gallery/src/lib/utils/img-recognizer.ts
  class ImgRecognizer (line 24) | class ImgRecognizer {
    method constructor (line 32) | constructor() {

FILE: projects/ng-gallery/src/lib/utils/item.class.ts
  type GalleryItem (line 4) | interface GalleryItem {
  class ImageItem (line 9) | class ImageItem implements GalleryItem {
    method constructor (line 13) | constructor(data: ImageItemData) {
  class VideoItem (line 19) | class VideoItem implements GalleryItem {
    method constructor (line 23) | constructor(data: VideoItemData) {
  class IframeItem (line 29) | class IframeItem implements GalleryItem {
    method constructor (line 33) | constructor(data: IframeItemData) {
  class YoutubeItem (line 39) | class YoutubeItem implements GalleryItem {
    method constructor (line 43) | constructor(data: YoutubeItemData) {
  class VimeoItem (line 55) | class VimeoItem implements GalleryItem {
    method constructor (line 59) | constructor(data: VimeoItemData) {
    method getVimeoThumb (line 72) | private getVimeoThumb(videoId: string): string {

FILE: projects/ng-gallery/src/stories/basic/Gallery.stories.ts
  type Story (line 247) | type Story = StoryObj<GalleryComponent>;

FILE: projects/ng-gallery/src/stories/custom-templates/CustomTemplates.stories.ts
  type Story (line 33) | type Story = StoryObj<CustomTemplateComponent>;

FILE: projects/ng-gallery/src/stories/custom-templates/custom-template.component.ts
  class CustomTemplateComponent (line 41) | class CustomTemplateComponent {

FILE: projects/ng-gallery/src/stories/lightbox/Lightbox.stories.ts
  type Story (line 24) | type Story = StoryObj<LightboxExampleComponent>;

FILE: projects/ng-gallery/src/stories/lightbox/lightbox-example.ts
  class LightboxExampleComponent (line 35) | class LightboxExampleComponent implements OnInit, OnDestroy {
    method constructor (line 43) | constructor(public gallery: Gallery, public lightbox: Lightbox) {
    method ngOnInit (line 46) | ngOnInit(): void {
    method ngOnDestroy (line 56) | ngOnDestroy(): void {

FILE: projects/ng-gallery/src/stories/pixabay/pixabay.model.ts
  type PixabayModel (line 1) | interface PixabayModel {
  type Hit (line 7) | interface Hit {
  type PixabayHDModel (line 30) | interface PixabayHDModel {
  type Hit2 (line 36) | interface Hit2 {

FILE: projects/ng-gallery/src/stories/pixabay/pixabay.service.ts
  constant API_KEY (line 4) | const API_KEY: string = '560162-704dd2880c027f22c62ab7941';
  function getHDImages (line 6) | function getHDImages(key: string): Promise<GalleryItem[]> {
  function getHDImagesForCustomTemplate (line 16) | function getHDImagesForCustomTemplate(key: string): Promise<GalleryItem[...
Condensed preview — 208 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (500K chars).
[
  {
    "path": ".editorconfig",
    "chars": 274,
    "preview": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size ="
  },
  {
    "path": ".eslintrc.json",
    "chars": 991,
    "preview": "{\n  \"root\": true,\n  \"ignorePatterns\": [\n    \"projects/**/*\"\n  ],\n  \"overrides\": [\n    {\n      \"files\": [\n        \"*.ts\"\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 739,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\n\n---\n\n<!-- \n1. Please make sure that you have searched in"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 742,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\n\n---\n\n<!-- \n1. Please make sure that you have searched"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.md",
    "chars": 717,
    "preview": "---\nname: Question\nabout: Ask a question\n\n---\n\n<!-- \n1. Please make sure that you have searched in the older issues befo"
  },
  {
    "path": ".github/workflows/integrate.yml",
    "chars": 1373,
    "preview": "name: CI Build\n\non:\n  pull_request:\n    branches: [ master ]\n  push:\n    branches: [ master ]\n\njobs:\n  build:\n    runs-o"
  },
  {
    "path": ".github/workflows/netlify.yml",
    "chars": 952,
    "preview": "name: Deploy Demo\n\non:\n  push:\n    branches: [ deploy-netlify ]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n  "
  },
  {
    "path": ".gitignore",
    "chars": 548,
    "preview": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# Compiled output\n/dist\n/tmp\n/out-tsc\n/bazel-"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 48863,
    "preview": "# Changelog\n\n## 13.0.0\n\n - Use native dialog to display the lightbox instead of Angular CDK overlay.  \n\n## 12.0.0-beta.5"
  },
  {
    "path": "LICENSE",
    "chars": 1085,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2016-2025 Murhaf Sousli\n\nPermission is hereby granted, free of charge, to any perso"
  },
  {
    "path": "README.md",
    "chars": 1948,
    "preview": "<p align=\"center\">\n  <img width=\"150px\" src=\"https://user-images.githubusercontent.com/8130692/36171173-ad0da54c-1112-11"
  },
  {
    "path": "angular.json",
    "chars": 4796,
    "preview": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \""
  },
  {
    "path": "package.json",
    "chars": 2800,
    "preview": "{\n  \"name\": \"ng-gallery-project\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"start-demo\": \"ng serve ngx-gallery-demo --ho"
  },
  {
    "path": "projects/ng-gallery/.eslintrc.json",
    "chars": 1349,
    "preview": "{\n  \"root\": true,\n  \"ignorePatterns\": [\n    \"projects/**/*\"\n  ],\n  \"overrides\": [\n    {\n      \"files\": [\n        \"*.ts\"\n"
  },
  {
    "path": "projects/ng-gallery/.storybook/colors.mdx",
    "chars": 774,
    "preview": "{/* Colors.mdx */}\n\nimport { Meta, ColorPalette, ColorItem } from '@storybook/blocks';\n\n<Meta title=\"Colors\" />\n\n<ColorP"
  },
  {
    "path": "projects/ng-gallery/.storybook/main.ts",
    "chars": 394,
    "preview": "import type { StorybookConfig } from \"@storybook/angular\";\n\nconst config: StorybookConfig = {\n  stories: [\"../src/**/*.m"
  },
  {
    "path": "projects/ng-gallery/.storybook/manager.js",
    "chars": 118,
    "preview": "import { addons } from '@storybook/manager-api';\nimport theme from './theme';\n\naddons.setConfig({\n  theme: theme,\n});\n"
  },
  {
    "path": "projects/ng-gallery/.storybook/preview.ts",
    "chars": 448,
    "preview": "import type { Preview } from \"@storybook/angular\";\nimport { setCompodocJson } from \"@storybook/addon-docs/angular\";\n\nimp"
  },
  {
    "path": "projects/ng-gallery/.storybook/theme.js",
    "chars": 922,
    "preview": "import { create } from '@storybook/theming/create';\n\nexport default create({\n  base: 'dark',\n  // Typography\n  fontBase:"
  },
  {
    "path": "projects/ng-gallery/.storybook/tsconfig.json",
    "chars": 296,
    "preview": "{\n  \"extends\": \"../tsconfig.lib.json\",\n  \"compilerOptions\": {\n    \"types\": [\"node\"],\n    \"allowSyntheticDefaultImports\":"
  },
  {
    "path": "projects/ng-gallery/.storybook/typings.d.ts",
    "chars": 77,
    "preview": "declare module '*.md' {\n  const content: string;\n  export default content;\n}\n"
  },
  {
    "path": "projects/ng-gallery/README.md",
    "chars": 1806,
    "preview": "<p align=\"center\">\n  <img width=\"150px\" src=\"https://user-images.githubusercontent.com/8130692/36171173-ad0da54c-1112-11"
  },
  {
    "path": "projects/ng-gallery/karma.conf.js",
    "chars": 1558,
    "preview": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-fi"
  },
  {
    "path": "projects/ng-gallery/lightbox/ng-package.json",
    "chars": 3,
    "preview": "{}\n"
  },
  {
    "path": "projects/ng-gallery/lightbox/src/gallerize.directive.ts",
    "chars": 5488,
    "preview": "import {\n  Directive,\n  Input,\n  OnInit,\n  OnDestroy,\n  Inject,\n  Optional,\n  Self,\n  Host,\n  NgZone,\n  ElementRef,\n} fr"
  },
  {
    "path": "projects/ng-gallery/lightbox/src/lightbox.animation.ts",
    "chars": 531,
    "preview": "import { animate, state, style, transition, trigger } from '@angular/animations';\n\nexport const lightboxAnimation = trig"
  },
  {
    "path": "projects/ng-gallery/lightbox/src/lightbox.component.scss",
    "chars": 2308,
    "preview": "@mixin fullscreen() {\n  width: 100%;\n\n  gallery {\n    max-width: unset;\n    max-height: unset;\n    //position: fixed;\n  "
  },
  {
    "path": "projects/ng-gallery/lightbox/src/lightbox.component.ts",
    "chars": 842,
    "preview": "import { Component, viewChild, contentChild, Signal, ElementRef, ChangeDetectionStrategy } from '@angular/core';\nimport "
  },
  {
    "path": "projects/ng-gallery/lightbox/src/lightbox.default.ts",
    "chars": 983,
    "preview": "import { LightboxConfig } from './lightbox.model';\n\nexport const defaultConfig: LightboxConfig = {\n  backdropClass: 'g-b"
  },
  {
    "path": "projects/ng-gallery/lightbox/src/lightbox.directive.ts",
    "chars": 931,
    "preview": "// import { Directive, Input, ElementRef, OnInit, OnDestroy, Renderer2 } from '@angular/core';\n// import { fromEvent, ta"
  },
  {
    "path": "projects/ng-gallery/lightbox/src/lightbox.model.ts",
    "chars": 799,
    "preview": "import { InjectionToken, Provider } from '@angular/core';\nimport { defaultConfig } from './lightbox.default';\n\nexport co"
  },
  {
    "path": "projects/ng-gallery/lightbox/src/lightbox.module.ts",
    "chars": 426,
    "preview": "import { NgModule } from '@angular/core';\nimport { GalleryModule } from 'ng-gallery';\n// import { LightboxDirective } fr"
  },
  {
    "path": "projects/ng-gallery/lightbox/src/lightbox.service.ts",
    "chars": 3124,
    "preview": "import { ComponentRef, inject, Injectable } from '@angular/core';\nimport { DomSanitizer } from '@angular/platform-browse"
  },
  {
    "path": "projects/ng-gallery/lightbox/src/public_api.ts",
    "chars": 223,
    "preview": "export * from './lightbox.model';\nexport * from './lightbox.component';\nexport * from './lightbox.service';\nexport * fro"
  },
  {
    "path": "projects/ng-gallery/ng-package.json",
    "chars": 160,
    "preview": "{\n  \"$schema\": \"../../node_modules/ng-packagr/ng-package.schema.json\",\n  \"dest\": \"../../dist/ng-gallery\",\n  \"lib\": {\n   "
  },
  {
    "path": "projects/ng-gallery/package.json",
    "chars": 712,
    "preview": "{\n  \"name\": \"ng-gallery\",\n  \"version\": \"13.0.0\",\n  \"homepage\": \"https://ngx-gallery.netlify.app/\",\n  \"author\": {\n    \"na"
  },
  {
    "path": "projects/ng-gallery/src/lib/auto-height/auto-height.spec.ts",
    "chars": 2415,
    "preview": "// import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\n// import { NoopAnimat"
  },
  {
    "path": "projects/ng-gallery/src/lib/auto-height/auto-height.ts",
    "chars": 3351,
    "preview": "// import {\n//   Directive,\n//   effect,\n//   inject,\n//   signal,\n//   untracked,\n//   WritableSignal,\n//   EffectClean"
  },
  {
    "path": "projects/ng-gallery/src/lib/autoplay/autoplay.directive.ts",
    "chars": 1911,
    "preview": "// import {\n//   Directive,\n//   inject,\n//   effect,\n//   untracked,\n//   EffectCleanupRegisterFn\n// } from '@angular/c"
  },
  {
    "path": "projects/ng-gallery/src/lib/bullets/gallery-bullets.component.ts",
    "chars": 1595,
    "preview": "import {\n  Component,\n  inject,\n  numberAttribute,\n  booleanAttribute,\n  input,\n  InputSignal,\n  ChangeDetectionStrategy"
  },
  {
    "path": "projects/ng-gallery/src/lib/bullets/gallery-bullets.scss",
    "chars": 1109,
    "preview": ":host {\n  position: absolute;\n  left: 50%;\n  z-index: 99;\n  transform: translateX(-50%);\n  display: flex;\n  gap: 6px;\n  "
  },
  {
    "path": "projects/ng-gallery/src/lib/bullets/gallery-bullets.spec.ts",
    "chars": 3747,
    "preview": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsMo"
  },
  {
    "path": "projects/ng-gallery/src/lib/core/gallery.component.ts",
    "chars": 8966,
    "preview": "import {\n  Component,\n  inject,\n  output,\n  booleanAttribute,\n  numberAttribute,\n  computed,\n  effect,\n  untracked,\n  in"
  },
  {
    "path": "projects/ng-gallery/src/lib/core/gallery.scss",
    "chars": 2824,
    "preview": ":host {\n  --g-height-transition: height 468ms cubic-bezier(0.42, 0, 0.58, 1);\n  --g-nav-drop-shadow: drop-shadow(0 0 2px"
  },
  {
    "path": "projects/ng-gallery/src/lib/core/gallery.spec.ts",
    "chars": 2873,
    "preview": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { DebugElement } f"
  },
  {
    "path": "projects/ng-gallery/src/lib/counter/gallery-counter.component.ts",
    "chars": 827,
    "preview": "import {\n  Component,\n  inject,\n  computed,\n  input,\n  Signal,\n  InputSignal,\n  ChangeDetectionStrategy\n} from '@angular"
  },
  {
    "path": "projects/ng-gallery/src/lib/counter/gallery-counter.scss",
    "chars": 809,
    "preview": ":host {\n  // Gallery position variables\n  --counter-top: unset;\n  --counter-bottom: unset;\n  --counter-border-radius: un"
  },
  {
    "path": "projects/ng-gallery/src/lib/counter/gallery-counter.spec.ts",
    "chars": 3041,
    "preview": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsMo"
  },
  {
    "path": "projects/ng-gallery/src/lib/debug/debug.scss",
    "chars": 2536,
    "preview": ":host[debug='true'] {\n  ::ng-deep {\n    .g-sliding, .g-resizing, .g-scrolling {\n      gallery-item.g-item-highlight {\n  "
  },
  {
    "path": "projects/ng-gallery/src/lib/directives/gallery-box-def.directive.spec.ts",
    "chars": 1224,
    "preview": "import { TestBed, ComponentFixture } from '@angular/core/testing';\nimport { Component, viewChild } from '@angular/core';"
  },
  {
    "path": "projects/ng-gallery/src/lib/directives/gallery-box-def.directive.ts",
    "chars": 646,
    "preview": "import { Directive, inject, TemplateRef } from '@angular/core';\nimport { GalleryConfig } from '../models/config.model';\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/directives/gallery-item-def.directive.spec.ts",
    "chars": 1354,
    "preview": "import { TestBed, ComponentFixture } from '@angular/core/testing';\nimport { Component, viewChild } from '@angular/core';"
  },
  {
    "path": "projects/ng-gallery/src/lib/directives/gallery-item-def.directive.ts",
    "chars": 1049,
    "preview": "import { Directive, inject, TemplateRef } from '@angular/core';\nimport { GalleryItemData } from '../templates/items.mode"
  },
  {
    "path": "projects/ng-gallery/src/lib/gallery.module.ts",
    "chars": 451,
    "preview": "import { NgModule } from '@angular/core';\nimport { GalleryComponent } from './core/gallery.component';\nimport { GalleryI"
  },
  {
    "path": "projects/ng-gallery/src/lib/gestures/hammer-slider.spec.ts",
    "chars": 1661,
    "preview": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsMo"
  },
  {
    "path": "projects/ng-gallery/src/lib/gestures/hammer-sliding.directive.ts",
    "chars": 6101,
    "preview": "import {\n  Directive,\n  inject,\n  signal,\n  effect,\n  untracked,\n  booleanAttribute,\n  input,\n  NgZone,\n  ElementRef,\n  "
  },
  {
    "path": "projects/ng-gallery/src/lib/gestures/mouse-sliding.directive.ts",
    "chars": 5268,
    "preview": "// import { Directive, Inject, Input, Output, OnChanges, OnDestroy, SimpleChanges, NgZone, ElementRef, EventEmitter } fr"
  },
  {
    "path": "projects/ng-gallery/src/lib/models/config.model.ts",
    "chars": 1519,
    "preview": "import { InjectionToken, Provider, TemplateRef } from '@angular/core';\nimport { BezierEasingOptions } from '../smooth-sc"
  },
  {
    "path": "projects/ng-gallery/src/lib/models/constants.ts",
    "chars": 727,
    "preview": "export enum IMAGE_SIZE {\n  Cover = 'cover',\n  Contain = 'contain'\n}\n\nexport enum LOADING_STRATEGY {\n  Preload = 'preload"
  },
  {
    "path": "projects/ng-gallery/src/lib/models/item.model.ts",
    "chars": 58,
    "preview": "export type ItemState = 'success' | 'loading' | 'failed';\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/models/slider.model.ts",
    "chars": 217,
    "preview": "export interface SliderState {\n  style: any;\n  instant: boolean;\n}\n\nexport interface WorkerState {\n  value: number;\n  in"
  },
  {
    "path": "projects/ng-gallery/src/lib/models/styles.model.ts",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "projects/ng-gallery/src/lib/nav/gallery-nav.component.ts",
    "chars": 1407,
    "preview": "import { Component, inject, computed, input, Signal, InputSignal, ChangeDetectionStrategy } from '@angular/core';\nimport"
  },
  {
    "path": "projects/ng-gallery/src/lib/nav/gallery-nav.scss",
    "chars": 1706,
    "preview": ":host {\n  // Gallery nav variables\n  --nav-space: 8px;\n  --nav-hover-space: 6.4px;\n  --nav-next-right: unset;\n  --nav-ne"
  },
  {
    "path": "projects/ng-gallery/src/lib/nav/gallery-nav.spec.ts",
    "chars": 2883,
    "preview": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsMo"
  },
  {
    "path": "projects/ng-gallery/src/lib/observers/intersection-directive.spec.ts",
    "chars": 3747,
    "preview": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsMo"
  },
  {
    "path": "projects/ng-gallery/src/lib/observers/intersection-observer.ts",
    "chars": 699,
    "preview": "import { Observable, Subscriber } from 'rxjs';\n\nexport function createIntersectionObserver(options: IntersectionObserver"
  },
  {
    "path": "projects/ng-gallery/src/lib/observers/intersection-sensor.directive.ts",
    "chars": 5365,
    "preview": "import {\n  Directive,\n  inject,\n  effect,\n  computed,\n  untracked,\n  Signal,\n  NgZone,\n  ElementRef,\n  EffectCleanupRegi"
  },
  {
    "path": "projects/ng-gallery/src/lib/services/gallery-ref.ts",
    "chars": 4349,
    "preview": "import { Injectable, computed, inject, signal, Signal, WritableSignal } from '@angular/core';\nimport { toObservable } fr"
  },
  {
    "path": "projects/ng-gallery/src/lib/services/hammer.ts",
    "chars": 564,
    "preview": "import { Injectable } from '@angular/core';\nimport { HammerGestureConfig } from '@angular/platform-browser';\n\nexport int"
  },
  {
    "path": "projects/ng-gallery/src/lib/services/resize-directive.spec.ts",
    "chars": 4015,
    "preview": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsMo"
  },
  {
    "path": "projects/ng-gallery/src/lib/services/resize-sensor.ts",
    "chars": 3281,
    "preview": "import {\n  Directive,\n  signal,\n  inject,\n  computed,\n  untracked,\n  afterRenderEffect,\n  NgZone,\n  Signal,\n  ElementRef"
  },
  {
    "path": "projects/ng-gallery/src/lib/services/scroll-snap-type.spec.ts",
    "chars": 3137,
    "preview": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsMo"
  },
  {
    "path": "projects/ng-gallery/src/lib/services/scroll-snap-type.ts",
    "chars": 874,
    "preview": "import { computed, Directive, inject, Signal } from '@angular/core';\nimport { HammerSliding } from '../gestures/hammer-s"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/adapters/base-adapter.ts",
    "chars": 872,
    "preview": "export abstract class SliderAdapter {\n\n  readonly abstract hammerDirection: number;\n\n  readonly abstract scrollSnapType:"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/adapters/index.ts",
    "chars": 65,
    "preview": "export * from './main-adapters';\nexport * from './base-adapter';\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/adapters/main-adapter.spec.ts",
    "chars": 10555,
    "preview": "import { Component, ElementRef, Signal, viewChild } from '@angular/core';\nimport { ComponentFixture, TestBed } from '@an"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/adapters/main-adapters.ts",
    "chars": 4713,
    "preview": "import { GalleryConfig } from '../../models/config.model';\nimport { SliderAdapter } from './base-adapter';\nimport { Smoo"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/gallery-slider.component.ts",
    "chars": 2665,
    "preview": "import {\n  Component,\n  inject,\n  output,\n  input,\n  viewChild,\n  InputSignal,\n  TemplateRef,\n  OutputEmitterRef,\n  Chan"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/gallery-slider.scss",
    "chars": 2001,
    "preview": ":host {\n  position: relative;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  overflow: hidden;\n  o"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/slider/slider.spec.ts",
    "chars": 2657,
    "preview": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsMo"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/slider/slider.ts",
    "chars": 1979,
    "preview": "import {\n  Component,\n  inject,\n  computed,\n  contentChildren,\n  booleanAttribute,\n  input,\n  Signal,\n  ElementRef,\n  In"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/slider-item/slider-item.scss",
    "chars": 1353,
    "preview": ":host {\n  cursor: var(--g-item-cursor);\n  height: var(--g-item-height);\n  width: var(--g-item-width);\n  max-height: var("
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/slider-item/slider-item.spec.ts",
    "chars": 2873,
    "preview": "import { ComponentFixture, TestBed } from '@angular/core/testing';\nimport { GalleryItemData } from 'ng-gallery';\nimport "
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/slider-item/slider-item.ts",
    "chars": 3130,
    "preview": "import {\n  Component,\n  inject,\n  signal,\n  computed,\n  input,\n  Signal,\n  Injector,\n  ElementRef,\n  InputSignal,\n  Temp"
  },
  {
    "path": "projects/ng-gallery/src/lib/smooth-scroll/bezier-easing.spec.ts",
    "chars": 1456,
    "preview": "import bezier from './bezier-easing';\n\ndescribe('Bezier Easing Function', () => {\n  it('should return a function', () =>"
  },
  {
    "path": "projects/ng-gallery/src/lib/smooth-scroll/bezier-easing.ts",
    "chars": 3773,
    "preview": "/**\n * https://github.com/gre/bezier-easing\n * BezierEasing - use bezier curve for transition easing function\n * by Gaët"
  },
  {
    "path": "projects/ng-gallery/src/lib/smooth-scroll/index.ts",
    "chars": 82,
    "preview": "export * from './smooth-scroll.directive';\nexport * from './smooth-scroll.model';\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/smooth-scroll/smooth-scroll.directive.ts",
    "chars": 9154,
    "preview": "import {\n  Directive,\n  inject,\n  signal,\n  effect,\n  untracked,\n  input,\n  NgZone,\n  ElementRef,\n  InputSignal,\n  Writa"
  },
  {
    "path": "projects/ng-gallery/src/lib/smooth-scroll/smooth-scroll.model.ts",
    "chars": 651,
    "preview": "import { _XAxis, _YAxis } from '@angular/cdk/scrolling';\n\nexport type SmoothScrollOptions = Partial<Pick<_XAxis, keyof _"
  },
  {
    "path": "projects/ng-gallery/src/lib/smooth-scroll/smooth-scroll.spec.ts",
    "chars": 3715,
    "preview": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsMo"
  },
  {
    "path": "projects/ng-gallery/src/lib/templates/gallery-iframe.component.ts",
    "chars": 1528,
    "preview": "// import {\n//   Component,\n//   inject,\n//   computed,\n//   input,\n//   viewChild,\n//   Signal,\n//   ElementRef,\n//   I"
  },
  {
    "path": "projects/ng-gallery/src/lib/templates/gallery-image.component.spec.ts",
    "chars": 5029,
    "preview": "import { ComponentFixture, TestBed } from '@angular/core/testing';\nimport { provideNoopAnimations } from '@angular/platf"
  },
  {
    "path": "projects/ng-gallery/src/lib/templates/gallery-image.component.ts",
    "chars": 3766,
    "preview": "import {\n  Component,\n  inject,\n  signal,\n  output,\n  computed,\n  input,\n  Signal,\n  InputSignal,\n  WritableSignal,\n  Ou"
  },
  {
    "path": "projects/ng-gallery/src/lib/templates/gallery-image.scss",
    "chars": 1880,
    "preview": ":host {\n  position: relative;\n  display: flex;\n  width: 100%;\n  height: 100%;\n  max-height: 100%;\n  max-width: 100%;\n  t"
  },
  {
    "path": "projects/ng-gallery/src/lib/templates/gallery-video.component.ts",
    "chars": 2744,
    "preview": "// import {\n//   Component,\n//   output,\n//   effect,\n//   computed,\n//   untracked,\n//   input,\n//   viewChild,\n//   Si"
  },
  {
    "path": "projects/ng-gallery/src/lib/templates/items.model.ts",
    "chars": 939,
    "preview": "type GalleryItemModel = {\n  // type?: GalleryItemType;\n  src?: string | { url: string, type: string }[];\n  thumb?: strin"
  },
  {
    "path": "projects/ng-gallery/src/lib/templates/svg-assets.ts",
    "chars": 1426,
    "preview": "export const imageFailedSvg = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<svg width=\"100\" height=\"100\" version=\"1.1\" viewBo"
  },
  {
    "path": "projects/ng-gallery/src/lib/tests/common.ts",
    "chars": 1457,
    "preview": "import { Component, Signal, viewChild } from '@angular/core';\nimport { GalleryComponent, GalleryItemData, GalleryItemDef"
  },
  {
    "path": "projects/ng-gallery/src/lib/tests/test-images.ts",
    "chars": 33658,
    "preview": "export const img1 = `data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg"
  },
  {
    "path": "projects/ng-gallery/src/lib/thumbs/gallery-thumbs.component.ts",
    "chars": 5018,
    "preview": "import {\n  Component,\n  inject,\n  computed,\n  numberAttribute,\n  booleanAttribute,\n  input,\n  contentChild,\n  Signal,\n  "
  },
  {
    "path": "projects/ng-gallery/src/lib/thumbs/gallery-thumbs.scss",
    "chars": 2201,
    "preview": ":host {\n  max-height: 100%;\n  max-width: 100%;\n  display: block;\n  z-index: 100;\n\n  // Forward thumb variables\n  --g-ite"
  },
  {
    "path": "projects/ng-gallery/src/lib/thumbs/gallery-thumbs.spec.ts",
    "chars": 5860,
    "preview": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsMo"
  },
  {
    "path": "projects/ng-gallery/src/lib/utils/gallery.default.ts",
    "chars": 2302,
    "preview": "import {\n  IMAGE_SIZE,\n  ORIENTATION,\n} from '../models/constants';\nimport { GalleryConfig } from '../models/config.mode"
  },
  {
    "path": "projects/ng-gallery/src/lib/utils/img-manager.spec.ts",
    "chars": 2699,
    "preview": "import { TestBed } from '@angular/core/testing';\nimport { BehaviorSubject } from 'rxjs';\nimport { GalleryRef } from '../"
  },
  {
    "path": "projects/ng-gallery/src/lib/utils/img-manager.ts",
    "chars": 1557,
    "preview": "import { inject, Injectable } from '@angular/core';\nimport { toObservable } from '@angular/core/rxjs-interop';\nimport { "
  },
  {
    "path": "projects/ng-gallery/src/lib/utils/img-recognizer.spec.ts",
    "chars": 3781,
    "preview": "import { Component, Signal, viewChild, } from '@angular/core';\nimport { ComponentFixture, TestBed } from '@angular/core/"
  },
  {
    "path": "projects/ng-gallery/src/lib/utils/img-recognizer.ts",
    "chars": 1494,
    "preview": "import {\n  Directive,\n  inject,\n  effect,\n  untracked,\n  ElementRef,\n  EffectCleanupRegisterFn\n} from '@angular/core';\ni"
  },
  {
    "path": "projects/ng-gallery/src/lib/utils/item.class.spec.ts",
    "chars": 2682,
    "preview": "import {\n  ITEM_TYPE,\n  IframeItemData,\n  ImageItemData,\n  VideoItemData,\n  VimeoItemData,\n  YoutubeItemData\n} from 'ng-"
  },
  {
    "path": "projects/ng-gallery/src/lib/utils/item.class.ts",
    "chars": 1889,
    "preview": "import { GalleryItemType, ITEM_TYPE } from '../models/constants';\nimport { IframeItemData, ImageItemData, VideoItemData,"
  },
  {
    "path": "projects/ng-gallery/src/public-api.ts",
    "chars": 961,
    "preview": "export * from './lib/services/gallery-ref';\nexport * from './lib/nav/gallery-nav.component';\nexport * from './lib/thumbs"
  },
  {
    "path": "projects/ng-gallery/src/stories/.eslintrc.json",
    "chars": 119,
    "preview": "{\n  \"rules\": {\n    \"@typescript-eslint/consistent-type-imports\": [\"error\", { \"disallowTypeAnnotations\": false }]\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/GettingStarted.mdx",
    "chars": 3122,
    "preview": "<div className=\"logo-container\">\n  <img src=\"https://user-images.githubusercontent.com/8130692/36171173-ad0da54c-1112-11"
  },
  {
    "path": "projects/ng-gallery/src/stories/LoadItems.mdx",
    "chars": 4380,
    "preview": "# Usage\n\nThere are several ways for configuring gallery items. Here are some examples:\n\n## Basic Example with Template B"
  },
  {
    "path": "projects/ng-gallery/src/stories/Responsiveness.mdx",
    "chars": 1466,
    "preview": "# Responsiveness\n\nTo modify the gallery configuration for smaller screens, including adjustments to thumbnail positionin"
  },
  {
    "path": "projects/ng-gallery/src/stories/basic/Bullets.mdx",
    "chars": 810,
    "preview": "import { Meta, Canvas } from \"@storybook/addon-docs\";\nimport * as GalleryStories from \"./Gallery.stories\";\n\n<Meta of={Ga"
  },
  {
    "path": "projects/ng-gallery/src/stories/basic/Counter.mdx",
    "chars": 531,
    "preview": "import { Meta, Canvas } from \"@storybook/addon-docs\";\nimport * as GalleryStories from \"./Gallery.stories\";\n\n<Meta of={Ga"
  },
  {
    "path": "projects/ng-gallery/src/stories/basic/Gallery.stories.ts",
    "chars": 11541,
    "preview": "import type { Meta, StoryObj } from '@storybook/angular';\nimport { applicationConfig } from '@storybook/angular';\nimport"
  },
  {
    "path": "projects/ng-gallery/src/stories/basic/Navigation.mdx",
    "chars": 422,
    "preview": "import { Meta, Canvas } from \"@storybook/addon-docs\";\nimport * as GalleryStories from \"./Gallery.stories\";\n\n<Meta of={Ga"
  },
  {
    "path": "projects/ng-gallery/src/stories/basic/Player.mdx",
    "chars": 2037,
    "preview": "import { Meta, Canvas } from \"@storybook/addon-docs\";\nimport * as GalleryStories from \"./Gallery.stories\";\n\n<Meta of={Ga"
  },
  {
    "path": "projects/ng-gallery/src/stories/basic/Slider.mdx",
    "chars": 3103,
    "preview": "import {Meta, Canvas} from \"@storybook/addon-docs\";\nimport * as GalleryStories from \"./Gallery.stories\";\n\n<Meta of={Gall"
  },
  {
    "path": "projects/ng-gallery/src/stories/basic/Thumbnails.mdx",
    "chars": 2328,
    "preview": "import { Meta, Canvas } from \"@storybook/addon-docs\";\nimport * as GalleryStories from \"./Gallery.stories\";\n\n<Meta of={Ga"
  },
  {
    "path": "projects/ng-gallery/src/stories/custom-templates/CustomTemplates.stories.ts",
    "chars": 3130,
    "preview": "import type { Meta, StoryObj } from '@storybook/angular';\nimport { applicationConfig, moduleMetadata } from '@storybook/"
  },
  {
    "path": "projects/ng-gallery/src/stories/custom-templates/CustomTemplatesUsage.mdx",
    "chars": 3338,
    "preview": "import {Meta, Canvas} from \"@storybook/addon-docs\";\nimport * as CustomTemplatesStories from \"./CustomTemplates.stories\";"
  },
  {
    "path": "projects/ng-gallery/src/stories/custom-templates/custom-template.component.ts",
    "chars": 1158,
    "preview": "import { Component, ChangeDetectionStrategy, Input } from '@angular/core';\nimport { CommonModule } from '@angular/common"
  },
  {
    "path": "projects/ng-gallery/src/stories/lightbox/CustomTemplates.mdx",
    "chars": 1089,
    "preview": "import {Meta, Canvas} from \"@storybook/addon-docs\";\nimport * as LightboxStories from \"./Lightbox.stories\";\n\n<Meta of={Li"
  },
  {
    "path": "projects/ng-gallery/src/stories/lightbox/Gallerize.mdx",
    "chars": 2796,
    "preview": "import {Meta, Canvas} from \"@storybook/addon-docs\";\nimport * as GalleryStories from \"./Lightbox.stories\";\n\n<Meta of={Gal"
  },
  {
    "path": "projects/ng-gallery/src/stories/lightbox/GettingStarted.mdx",
    "chars": 1266,
    "preview": "import {Meta, Canvas} from \"@storybook/addon-docs\";\nimport * as LightboxStories from \"./Lightbox.stories\";\n\n<Meta of={Li"
  },
  {
    "path": "projects/ng-gallery/src/stories/lightbox/Lightbox.mdx",
    "chars": 2742,
    "preview": "import {Meta, Canvas} from \"@storybook/addon-docs\";\nimport * as LightboxStories from \"./Lightbox.stories\";\n\n<Meta of={Li"
  },
  {
    "path": "projects/ng-gallery/src/stories/lightbox/Lightbox.stories.ts",
    "chars": 2314,
    "preview": "import type { Meta, StoryObj } from '@storybook/angular';\nimport { applicationConfig, moduleMetadata } from '@storybook/"
  },
  {
    "path": "projects/ng-gallery/src/stories/lightbox/lightbox-example.ts",
    "chars": 1391,
    "preview": "import { Component, Input, OnDestroy, OnInit, ChangeDetectionStrategy } from '@angular/core';\nimport { CommonModule } fr"
  },
  {
    "path": "projects/ng-gallery/src/stories/pixabay/pixabay.model.ts",
    "chars": 1040,
    "preview": "export interface PixabayModel {\n  total: number;\n  totalHits: number;\n  hits: Hit[];\n}\n\nexport interface Hit {\n  id: num"
  },
  {
    "path": "projects/ng-gallery/src/stories/pixabay/pixabay.service.ts",
    "chars": 1252,
    "preview": "import { ImageItem, GalleryItem } from 'ng-gallery';\nimport { Hit2, PixabayHDModel } from './pixabay.model';\n\nconst API_"
  },
  {
    "path": "projects/ng-gallery/src/test.ts",
    "chars": 525,
    "preview": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js';\nimp"
  },
  {
    "path": "projects/ng-gallery/tsconfig.lib.json",
    "chars": 314,
    "preview": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"../../tsconfig.json\",\n  \"co"
  },
  {
    "path": "projects/ng-gallery/tsconfig.lib.prod.json",
    "chars": 240,
    "preview": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.lib.json\",\n  \"co"
  },
  {
    "path": "projects/ng-gallery/tsconfig.spec.json",
    "chars": 273,
    "preview": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"../../tsconfig.json\",\n  \"co"
  },
  {
    "path": "projects/ng-gallery-demo/public/icons/browserconfig.xml",
    "chars": 246,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n    <msapplication>\n        <tile>\n            <square150x150logo"
  },
  {
    "path": "projects/ng-gallery-demo/public/icons/site.webmanifest.json",
    "chars": 456,
    "preview": "{\n    \"name\": \"Angular Gallery\",\n    \"short_name\": \"Angular Gallery\",\n    \"icons\": [\n        {\n            \"src\": \"/andr"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/app-routing.animations.ts",
    "chars": 515,
    "preview": "import { trigger, animate, transition, style, query } from '@angular/animations';\n\nexport const fadeAnimation = trigger("
  },
  {
    "path": "projects/ng-gallery-demo/src/app/app.component.html",
    "chars": 857,
    "preview": "<ng-progress ngProgressRouter ngProgressHttp/>\n\n<mat-sidenav-container>\n  <mat-sidenav #sidenav>\n    <div class=\"logo\">\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/app.component.scss",
    "chars": 1244,
    "preview": "@use \"../mixin\";\n\n:host {\n  display: block;\n  height: 100%;\n}\n\nh3 {\n  text-align: center;\n  letter-spacing: 1px;\n}\n\n.mai"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/app.component.ts",
    "chars": 2157,
    "preview": "import { Component, ViewChild, OnInit, ChangeDetectionStrategy } from '@angular/core';\nimport { NavigationEnd, Router, R"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/app.config.server.ts",
    "chars": 350,
    "preview": "import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';\nimport { provideServerRendering } from '@angu"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/app.config.ts",
    "chars": 1481,
    "preview": "import { ApplicationConfig } from '@angular/core';\nimport { provideRouter, withHashLocation } from '@angular/router';\nim"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/app.routes.ts",
    "chars": 1228,
    "preview": "import { Routes } from '@angular/router';\n\nexport const appRoutes: Routes = [\n  {\n    path: '',\n    loadComponent: () =>"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/doc-core/doc-core.component.html",
    "chars": 1724,
    "preview": "<section>\n  <section-title>Installation</section-title>\n  <p>Install with NPM</p>\n\n  <div class=\"install\">\n    <b>NPM</b"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/doc-core/doc-core.component.scss",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/doc-core/doc-core.component.ts",
    "chars": 1030,
    "preview": "import { ChangeDetectionStrategy, Component } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/doc-lightbox/doc-lightbox.component.html",
    "chars": 1081,
    "preview": "<section>\n  <section-title>Installation</section-title>\n  <p>Install with NPM</p>\n\n  <div class=\"install\">\n    <b>NPM</b"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/doc-lightbox/doc-lightbox.component.scss",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/doc-lightbox/doc-lightbox.component.ts",
    "chars": 1133,
    "preview": "import { ChangeDetectionStrategy, Component } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/documentation.component.html",
    "chars": 1615,
    "preview": "<div class=\"page-title\">\n  <h1>Getting Started</h1>\n</div>\n\n<div class=\"page-content\">\n\n  <div fxLayout fxLayout.lt-md=\""
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/documentation.component.scss",
    "chars": 450,
    "preview": ".page-content {\n  max-width: 1200px;\n}\n\n.doc-menu-container {\n  margin-top: 1em;\n}\n\n.doc-menu {\n  position: sticky;\n  to"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/documentation.component.ts",
    "chars": 906,
    "preview": "import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';\nimport { Title } from '@angular/platform-bro"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/routes.ts",
    "chars": 629,
    "preview": "import { Routes } from '@angular/router';\nimport { DocumentationComponent } from './documentation.component';\n\nexport co"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/examples/basic-example/basic-example.ts",
    "chars": 571,
    "preview": "import { ChangeDetectionStrategy, Component } from '@angular/core';\nimport { GalleryComponent, GalleryItemDef } from 'ng"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/gallerize-example/gallerize-example.component.html",
    "chars": 3191,
    "preview": "<div class=\"page-title\">\n  <h1>Gallerize Directive</h1>\n</div>\n<div class=\"page-content\">\n  <section>\n    <section-title"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/gallerize-example/gallerize-example.component.scss",
    "chars": 419,
    "preview": "img {\n  width: 100%;\n}\n\n.row {\n  margin: 3em 0;\n}\n\n#gallerize-ex {\n  margin: 3em 0;\n\n  ::ng-deep {\n    .g-image-item {\n "
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/gallerize-example/gallerize-example.component.ts",
    "chars": 2665,
    "preview": "import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';\nimport { NgFor, AsyncPipe, SlicePipe } from "
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/gallery-example/gallery-example.component.html",
    "chars": 3159,
    "preview": "<div class=\"page-title\">\n  <h1>Gallery Component</h1>\n</div>\n<div class=\"page-content\">\n\n  <section>\n    <section-title>"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/gallery-example/gallery-example.component.scss",
    "chars": 199,
    "preview": "gallery {\n  margin-top: 3em;\n  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);\n  border-radius: 0.5em;\n}\n\ncode {\n  margin: 0;"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/gallery-example/gallery-example.component.ts",
    "chars": 4212,
    "preview": "import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';\nimport { AsyncPipe } from '@angular/common';"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/home/home.component.html",
    "chars": 979,
    "preview": "<div style=\"height: 100%; overflow: auto\">\n  <div class=\"page-title\">\n    <h1>Ng-gallery</h1>\n    <h2>Simplifies the pro"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/home/home.component.scss",
    "chars": 1107,
    "preview": "@use \"../../../mixin\";\n\ngallery {\n  height: 700px;\n}\n\n.mat-mdc-raised-button:not(:disabled) {\n  font-weight: 500;\n  lett"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/home/home.component.ts",
    "chars": 1898,
    "preview": "import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';\nimport { NgIf, AsyncPipe } from '@angular/co"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/lab/lab.component.html",
    "chars": 11294,
    "preview": "<div class=\"page-title\">\n  <h1 fxLayout>\n    <mat-icon style=\"font-size: 56px; height: 56px; width: 56px;\">biotech</mat-"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/lab/lab.component.scss",
    "chars": 771,
    "preview": ".page-content {\n  padding-bottom: 50px;\n}\n\nbutton {\n  margin: 0.8em 0.5em;\n}\n\ngallery {\n  margin-top: 3em;\n  border-radi"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/lab/lab.component.ts",
    "chars": 4459,
    "preview": "import { ChangeDetectionStrategy, Component, inject, OnInit, Signal, signal, WritableSignal } from '@angular/core';\nimpo"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/lightbox-example/lightbox-example.component.html",
    "chars": 3794,
    "preview": "<div class=\"page-title\">\n  <h1>Lightbox Component</h1>\n</div>\n<div class=\"page-content\">\n\n  <!--  <section>-->\n  <!--   "
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/lightbox-example/lightbox-example.component.scss",
    "chars": 777,
    "preview": ":host {\n  .mdc-button {\n    ::ng-deep {\n      .mdc-button__label {\n        height: 100%;\n        width: 100%;\n      }\n  "
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/lightbox-example/lightbox-example.component.ts",
    "chars": 3918,
    "preview": "import { ChangeDetectionStrategy, Component, inject, OnDestroy, OnInit, Signal } from '@angular/core';\nimport { RouterLi"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/not-found/not-found.component.html",
    "chars": 203,
    "preview": "<div class=\"not-found\" fxLayout=\"column\" fxLayoutAlign=\"center center\">\n  <div>\n    <i class=\"fa fa-exclamation-triangle"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/not-found/not-found.component.scss",
    "chars": 127,
    "preview": ":host {\n  flex: 1;\n}\n\n.not-found {\n  font-size: 2em;\n  height: 100%;\n\n  i {\n    font-size: 5em;\n    margin-bottom: 20px;"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/not-found/not-found.component.ts",
    "chars": 607,
    "preview": "import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';\nimport { Title } from '@angular/platform-bro"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/templates-example/slide-text.animation.ts",
    "chars": 444,
    "preview": "import { trigger, style, transition, animate, state } from '@angular/animations';\n\nexport const slideInAnimation = trigg"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/templates-example/templates-example.component.html",
    "chars": 4472,
    "preview": "<div class=\"page-title\">\n  <h1>Custom Templates Example</h1>\n</div>\n<div class=\"page-content\">\n  <section>\n    <section-"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/templates-example/templates-example.component.scss",
    "chars": 949,
    "preview": "::ng-deep {\n  #mixed {\n    margin: 2em 0 3em;\n    box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);\n    border-radius: 0.5em;\n\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/templates-example/templates-example.component.ts",
    "chars": 7556,
    "preview": "import { Component, effect, OnInit, Signal, viewChild } from '@angular/core';\nimport { Title } from '@angular/platform-b"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/service/pixabay.model.ts",
    "chars": 1040,
    "preview": "export interface PixabayModel {\n  total: number;\n  totalHits: number;\n  hits: Hit[];\n}\n\nexport interface Hit {\n  id: num"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/service/pixabay.service.ts",
    "chars": 1020,
    "preview": "import { inject, Injectable } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { Observab"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/badges/badges.component.html",
    "chars": 1585,
    "preview": "<!--GITHUB STAR-->\n<iframe src=\"https://ghbtns.com/github-btn.html?user=murhafsousli&repo=ngx-gallery&type=star&count=tr"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/badges/badges.component.ts",
    "chars": 385,
    "preview": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'badges',\n  templateUrl: './badges.component.html',"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/footer/footer.component.html",
    "chars": 875,
    "preview": "<div fxLayout=\"column\" fxLayoutAlign=\"center center\">\n\n  <h4><p>Images from <a href=\"https://pixabay.com/\">Pixabay</a></"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/footer/footer.component.scss",
    "chars": 630,
    "preview": ":host {\n  box-shadow: inset 0 3px 8px 0 rgba(0, 0, 0, 0.08);\n  background-color: var(--primary-color);\n  display: flex;\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/footer/footer.component.ts",
    "chars": 538,
    "preview": "import { ChangeDetectionStrategy, Component } from '@angular/core';\nimport { DatePipe } from '@angular/common';\nimport {"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/gallery-mock-dialog.ts",
    "chars": 1043,
    "preview": "import { Component, Inject } from '@angular/core';\nimport { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from '@angu"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/hl-code/hl-code.component.html",
    "chars": 716,
    "preview": "@if (disabled) {\n  <div class=\"code-wrapper\">\n    <pre>\n      <code class=\"hljs\" [innerHTML]=\"code\"></code>\n    </pre>\n "
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/hl-code/hl-code.component.scss",
    "chars": 946,
    "preview": "@use '../../../mixin';\n\n:host {\n  display: flex;\n  flex-direction: column;\n  position: relative;\n  min-width: 100%;\n  ma"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/hl-code/hl-code.component.ts",
    "chars": 1016,
    "preview": "import { Component, Input, inject, ChangeDetectionStrategy, booleanAttribute } from '@angular/core';\nimport { ClipboardM"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/menu/menu.component.html",
    "chars": 784,
    "preview": "<div class=\"menu\" [class.menu-toolbar]=\"toolbar\">\n  <a mat-button routerLink=\".\" routerLinkActive=\"active\" [routerLinkAc"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/menu/menu.component.scss",
    "chars": 485,
    "preview": "@use \"../../../mixin\";\n\n:host {\n  margin: 0;\n  flex: 0 0 auto;\n  padding: 0;\n}\n\n.menu.menu-toolbar {\n  flex-direction: r"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/menu/menu.component.ts",
    "chars": 574,
    "preview": "import { Component, ChangeDetectionStrategy, Input } from '@angular/core';\nimport { RouterLink, RouterLinkActive } from "
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/note/note.component.scss",
    "chars": 754,
    "preview": ":host {\n  display: block;\n\n  &.info {\n    section {\n      background-color: #a7daff;\n    }\n\n    .note-icon {\n      backg"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/note/note.component.ts",
    "chars": 454,
    "preview": "import { Component } from '@angular/core';\nimport { MatIconModule } from '@angular/material/icon';\n\n@Component({\n  selec"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/pipes/keys.pipe.ts",
    "chars": 322,
    "preview": "import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n  name: 'keys',\n  standalone: true\n})\nexport class KeysPip"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/section-title/section-title.component.scss",
    "chars": 130,
    "preview": ":host {\n  width: 100%;\n}\n\nh2 {\n  flex: 1;\n  margin: 2em 0 1em;\n}\n\nfa-icon {\n  color: var(--accent-color);\n  margin-right"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/section-title/section-title.component.ts",
    "chars": 740,
    "preview": "import { ChangeDetectionStrategy, Component } from '@angular/core';\nimport { faCaretRight } from '@fortawesome/free-soli"
  },
  {
    "path": "projects/ng-gallery-demo/src/index.html",
    "chars": 2459,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n\n  <meta charset=\"utf-8\">\n  <title>Angular Gallery</title>\n  <base href=\"/\">\n\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/main.server.ts",
    "chars": 264,
    "preview": "import { bootstrapApplication } from '@angular/platform-browser';\nimport { AppComponent } from './app/app.component';\nim"
  }
]

// ... and 8 more files (download for full content)

About this extraction

This page contains the full source code of the MurhafSousli/ngx-gallery GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 208 files (454.5 KB), approximately 141.4k tokens, and a symbol index with 248 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!