[
  {
    "path": ".editorconfig",
    "content": "# Editor configuration, see https://editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.ts]\nquote_type = single\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".eslintrc.json",
    "content": "{\n  \"root\": true,\n  \"ignorePatterns\": [\n    \"projects/**/*\"\n  ],\n  \"overrides\": [\n    {\n      \"files\": [\n        \"*.ts\"\n      ],\n      \"extends\": [\n        \"eslint:recommended\",\n        \"plugin:@typescript-eslint/recommended\",\n        \"plugin:@angular-eslint/recommended\",\n        \"plugin:@angular-eslint/template/process-inline-templates\"\n      ],\n      \"rules\": {\n        \"@angular-eslint/directive-selector\": [\n          \"error\",\n          {\n            \"type\": \"attribute\",\n            \"prefix\": \"app\",\n            \"style\": \"camelCase\"\n          }\n        ],\n        \"@angular-eslint/component-selector\": [\n          \"error\",\n          {\n            \"type\": \"element\",\n            \"prefix\": \"app\",\n            \"style\": \"kebab-case\"\n          }\n        ]\n      }\n    },\n    {\n      \"files\": [\n        \"*.html\"\n      ],\n      \"extends\": [\n        \"plugin:@angular-eslint/template/recommended\",\n        \"plugin:@angular-eslint/template/accessibility\"\n      ],\n      \"rules\": {}\n    }\n  ]\n}\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\n\n---\n\n<!-- \n1. Please make sure that you have searched in the older issues before submitting a new one!\n2. Please fill out all the required information!\n -->\n\n\n#### What is the expected behavior?\n\n\n#### What is the current behavior?\n\n\n#### What are the steps to reproduce?\n\n<!-- \nProviding a StackBlitz reproduction is the *best* way to share your issue. <br/>\nStackBlitz starter: https://stackblitz.com/edit/ngx-gallery<br/>\n-->\n\n\n#### What is the use-case or motivation for changing an existing behavior?\n\n\n\n#### Which versions are you using for the following packages?\n\nAngular:\nAngular CDK:\nAngular CLI:\nTypescript:\nGallery: \n\n\n#### Is there anything else we should know?\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\n\n---\n\n<!-- \n1. Please make sure that you have searched in the older issues before submitting a new one!\n2. Please fill out all the required information!\n -->\n\n\n#### What is the expected behavior?\n\n\n#### What is the current behavior?\n\n\n#### What are the steps to reproduce?\n\n<!-- \nProviding a StackBlitz reproduction is the *best* way to share your issue. <br/>\nStackBlitz starter: https://stackblitz.com/edit/ngx-gallery<br/>\n-->\n\n\n#### What is the use-case or motivation for changing an existing behavior?\n\n\n\n#### Which versions are you using for the following packages?\n\nAngular:\nAngular CDK:\nAngular CLI:\nTypescript:\nGallery: \n\n\n#### Is there anything else we should know?\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.md",
    "content": "---\nname: Question\nabout: Ask a question\n\n---\n\n<!-- \n1. Please make sure that you have searched in the older issues before submitting a new one!\n2. Please fill out all the required information!\n -->\n\n\n#### What is the expected behavior?\n\n\n#### What is the current behavior?\n\n\n#### What are the steps to reproduce?\n\n<!-- \nProviding a StackBlitz reproduction is the *best* way to share your issue. <br/>\nStackBlitz starter: https://stackblitz.com/edit/ngx-gallery<br/>\n-->\n\n\n#### What is the use-case or motivation for changing an existing behavior?\n\n\n\n#### Which versions are you using for the following packages?\n\nAngular:\nAngular CDK:\nAngular CLI:\nTypescript:\nGallery: \n\n\n#### Is there anything else we should know?\n"
  },
  {
    "path": ".github/workflows/integrate.yml",
    "content": "name: CI Build\n\non:\n  pull_request:\n    branches: [ master ]\n  push:\n    branches: [ master ]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@master\n\n      - name: Use Node.js 20\n        uses: actions/setup-node@master\n        with:\n          node-version: 20\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Build\n        run: npm run build-lib\n\n#      - name: Lint\n#        run: npm run lint-lib\n\n      - name: Test\n        run: npm run test-lib-headless\n\n      - name: Upload coverage reports to Codecov\n        uses: codecov/codecov-action@main\n        with:\n          token: ${{ secrets.CODECOV_TOKEN }}\n          slug: MurhafSousli/ngx-gallery\n\n      - name: Code Coverage Report\n        uses: irongut/CodeCoverageSummary@master\n        with:\n          filename: coverage/**/cobertura-coverage.xml\n          badge: true\n          fail_below_min: true\n          format: markdown\n          hide_branch_rate: false\n          hide_complexity: true\n          indicators: true\n          output: both\n          thresholds: '60 80'\n\n      - name: Add Coverage PR Comment\n        uses: marocchino/sticky-pull-request-comment@main\n        if: github.event_name == 'pull_request'\n        with:\n          recreate: true\n          path: code-coverage-results.md\n        continue-on-error: true  # Allow this step to fail\n"
  },
  {
    "path": ".github/workflows/netlify.yml",
    "content": "name: Deploy Demo\n\non:\n  push:\n    branches: [ deploy-netlify ]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - name: Use Node.js 20\n        uses: actions/setup-node@v1\n        with:\n          node-version: 20\n      - name: Install dependencies\n        run: npm ci\n      - name: Build demo\n        run: npm run build-demo\n      - name: Deploy to Netlify\n        uses: nwtgck/actions-netlify@v1.1.11\n        with:\n          publish-dir: './dist/ng-gallery-demo'\n          production-branch: deploy-netlify\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          deploy-message: \"Deploy from GitHub Actions\"\n          enable-pull-request-comment: true\n          enable-commit-comment: true\n          overwrites-pull-request-comment: true\n        env:\n          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}\n          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}\n        timeout-minutes: 1\n"
  },
  {
    "path": ".gitignore",
    "content": "# 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-out\n\n# Node\n/node_modules\nnpm-debug.log\nyarn-error.log\n\n# IDEs and editors\n.idea/\n.project\n.classpath\n.c9/\n*.launch\n.settings/\n*.sublime-workspace\n\n# Visual Studio Code\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n.history/*\n\n# Miscellaneous\n/.angular/cache\n.sass-cache/\n/connect.lock\n/coverage\n/libpeerconnection.log\ntestem.log\n/typings\n\n# System files\n.DS_Store\nThumbs.db\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# 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\n\n- feat: Add `provideGalleryOptions` and `provideLightboxOptions` to set global options.\n- refactor: Use `useFactory` function to set the default options for `GALLERY_CONFIG` and `LIGHTBOX_CONFIG` token.\n\n## 12.0.0\n\n- Add vimeo support in [#575](https://github.com/MurhafSousli/ngx-gallery/pull/575).\n\n## 12.0.0-beta.4\n\n> See the [storybook documentation](https://ngx-gallery-next.netlify.app/)\n\n- feat: Add RTL support, closes [#540](https://github.com/MurhafSousli/ngx-gallery/issues/540).\n- All boolean inputs of `<gallery>` components can be used as string attributes\n  - e.g. `<gallery autoHeight>`, `<gallery autoHeight=\"true\">` and `<gallery [autoHeight]=\"true\">` sets the option's value to true.\n  - e.g. `<gallery autoHeight=\"false\">` and `<gallery [autoHeight]=\"false\">` sets the option's value to false.\n- All number inputs of `<gallery>` components can be used as string attributes\n  - e.g. `<gallery playerInterval=\"2000\">` and `<gallery [playerInterval]=\"2000\">` sets the option's value to 2000\n\n**Improved performance**\n\n- refactor: Replace the scroll event with intersection observer to detect the active item while scrolling.\n\n**ItemAutoSize, ThumbAutoSize features**\n\n- enhance: Toggling `itemAutoSize` option is now reactive.\n- fix: `[thumbAutosize]` causes random invalid starting thumbnail scroller position when scrolling possible, closes [#521](https://github.com/MurhafSousli/ngx-gallery/issues/521)\n- fix: `[ItemAutosize]` in website/safari browsers do not work as expected, closes [#543](https://github.com/MurhafSousli/ngx-gallery/issues/543)\n\n**AutoHeight feature**\n\n- enhance: Auto-height feature is not more precise and works well with or without height transition\n- fix: Auto-height issue when screen size changes\n\n**Autoplay feature**\n\n- fix: `autoplay` resets the timer after navigated.\n- fix: `autoplay` only start the timer after the image is loaded.\n\n**Bullets (previously named 'Dots')**\n\n- feat: `disableBullets` disable bullets' clicks\n\n**Custom template**\n\n- feature: Introduce `galleryImage` directive within `galleryItemDef`, to allow recognizing the img element in your custom item template.\n\n### Breaking changes\n\n#### Options renamed:\n\n**Core**\n- `slidingDirection` → `orientation`\n- `slidingEase` → `scrollEase`\n- `slidingDuration` → `scrollDuration`\n- `slidingDisabled` → `disableScroll`\n- `mouseSlidingDisabled` → `disableMouseScroll`\n- `autoPlay` → `autoplay`\n\n**Thumbs**\n- `thumb` → `thumbs`\n- `thumbMode` → `thumbCentralized`\n- `thumbMode` → `thumbCentralized`\n- `thumbDetached` → `detachThumbs`\n- `thumbSlidingDisabled` → `disableThumbMouseScroll`\n- `thumbMouseSlidingDisabled` → `disableThumbMouseScroll`\n\n**Bullets**\n- `dots` → `bullets`\n- `dotSize` → `bulletSize`\n- `dotPosition` → `bulletPosition`\n\n#### Input removed (no longer exist)\n\n- `navScrollBehavior` the option is now removed, use `scrollBehavior` instead.\n\n\n***\n\n## 11.0.0\n\n- feat: Add `galleryThumbDef`, `galleryImageDef`, `galleryItemDef`, `galleryBoxDef` to set custom templates, closes [#487](https://github.com/MurhafSousli/ngx-gallery/issues/487).\n- feat: Add `imageTemplate` property to `GalleryConfig`.\n- feat: Add `args` property in case need to attach extra data with the gallery item.\n- enhance: Improve overall typings.\n\n### Breaking changes\n\n- Usage of setting custom template has been changed! see the [wiki page](https://github.com/MurhafSousli/ngx-gallery/wiki) for more info.\n- The inputs `itemTemplate`, `thumbTemplate` and `boxTemplate` has been removed from the gallery component, however they still exist in `GalleryConfig`\n\n\n## 10.0.0\n\n- feat: Migrate to standalone components.\n\n### Breaking Changes\n\n- Both `GalleryModule` and `LightboxModule` no longer provide the `withConfig()` method.\n\n## 9.0.1\n\n- 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).\n\n## 9.0.0\n\n- Upgrade to Angular 16\n\n## 8.0.4\n\n- fix(core): Fix `VideoItem` typo, closes [#529](https://github.com/MurhafSousli/ngx-gallery/issues/529).\n- 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).\n\n## 8.0.3\n\n- fix(core): SSR error, closes [532](https://github.com/MurhafSousli/ngx-gallery/issues/532) in [#533](https://github.com/MurhafSousli/ngx-gallery/pull/533).\n\n## 8.0.2\n\n- 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).\n- 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).\n\n## 8.0.1\n\n- fix(core): Gallery nav icons are not alignment properly, in [d4dca8b](https://github.com/MurhafSousli/ngx-gallery/pull/506/commits/d4dca8b4f68b4439471ed7f90ea4e23c27ff0c41).\n- fox(core): Gallery dots is not horizontally centralized, in [f2d6910](https://github.com/MurhafSousli/ngx-gallery/pull/506/commits/f2d691083350adb64ea4b9c205e6fa3c31f6c0a3).\n- fix(core): Fix lib's angular peerDependencies version to >=15 in [9ea5ea3](https://github.com/MurhafSousli/ngx-gallery/pull/506/commits/9ea5ea34f77e6329f9385c7db056cc637557b1bc).\n\n## 8.0.0\n\n- feat(core): Add `isActive` to custom gallery template context, in [0b3f8bf](https://github.com/MurhafSousli/ngx-gallery/pull/497/commits/0b3f8bf43e383a8ee3e53a3140e18b8bcf1c2d69).\n- refactor(core): Fix the iframe error regarding the `allow` attribute.\n- refactor(core): Change default navigation icons.\n- refactor(core): Change default dots size.\n- refactor(core): Change default counter styles.\n- regret(core): Remove`itemLoaded` output.\n\n\n## 8.0.0-beta.5\n\n- regret(core): Remove`contentVisibilityAuto` option for version 8.\n- ~~feat(core): Add `itemLoaded` output which emits after an item is loaded, for image items it emits after the image is loaded.~~\n- feat(core): Add `autoHeight` option, when set to true, the gallery height will fit the active item height.\n- feat(core): Add `autoItemSize` option, when set to true, the item will fit its image aspect ratio.\n- feat(core): Add `autoThumbSize` option, when set to true, the thumb will fit its image aspect ratio.\n- feat(core): Add `scrollBehavior` option.\n- feat(core): Add `navScrollBehavior` option.\n- feat(core): Add `thumbImageSize` option.\n- feat(core): Add more options to the video item.\n- feat(core): Add `configSnapshot` to `GalleryRef` class.\n- feat(core): Add an optional parameter `behavior` to all `next(behavior?)`, `prev(behavior?)`, `set(index, behavior?)` functions, fallbacks to the `scrollBehavior` config.\n- refactor(core): Only display custom item template container when `itemTemplate` is provided.\n\n## 8.0.0-beta.4\n\n- ~~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).~~\n- feat(core): Use native `loading` attribute on all `img` and `iframe` for native lazy loading.\n- refactor(core): Fix loop issue when sliding with using the mouse, in [1572bea](https://github.com/MurhafSousli/ngx-gallery/pull/491/commits/1572beae2bc58792fac94243f4f3e20c0a61e549).\n- refactor(core): Remove `lazy-image` directive.\n\n## 8.0.0-beta.3\n\n- 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).\n\n## 8.0.0-beta.2\n\n- fix(lightbox): close button is not displayed, in [506249b](https://github.com/MurhafSousli/ngx-gallery/pull/490/commits/506249bbc5877cd4ed54cf610a42b3a31abcb417).\n\n## 8.0.0-beta.1\n\n- feat(core): Use scrolling slider instead of transform method, allows touchpad scroll to slide the gallery as well as native sliding on mobile browser.\n- 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).\n- feat(core): Add `slidingEase` and `slidingDuration` to customize sliding ease and duration in [4c1db03](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/4c1db03afb3a27896cd6dad5d08f91612bfa75a2).\n- 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).\n- 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).\n- 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).\n- 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).\n- 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).\n- 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).\n- refactor(core): Remove `thumbMode` option from the gallery, in [18f71e3](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/18f71e3599f7af1a20f1482307a6370c0c8b6f05)\n- refactor(core): Remove `tapClick` event and use native `click` event, in [3d960cc](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/3d960cca3b4954c999e10a86b0993d45d5c8462f).\n- refactor(core): Remove `ng-content` from the gallery, in [63e3b6b](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/63e3b6b5110ee5eaab6452cf775c3488023bd7d9).\n- refactor(core): Remove `panSensitivity` option, in [d1f8d34](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/d1f8d342a597d88afe66681b4ae099362273e03c).\n- refactor(core): Remove `gestures` option, in [70cb00c](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/70cb00c5aa808ea33aba1b674666ab0f99501a9d).\n- refactor(core): Remove `reserveGesturesAction` option, in [4b07fc7](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/4b07fc7ca4ef3d50e59accfe11593ee3a84ee706).\n- refactor(core): Remove `zoomOut` option, in [19ba2b8](https://github.com/MurhafSousli/ngx-gallery/pull/488/commits/19ba2b8c8d4402085c0b400b3e4c0e014d2e5abb).\n\n### Breaking changes:\n\nHammerJs is only used for sliding using the mouse on desktop only, Sliding on mobile devices is now native scroll.\n\n- `gestures` option has been deprecated.\n- `thumbMode` option has been deprecated, sliding thumbnails is free.\n- `zoomOut` has been deprecated.\n- `reserveGesturesAction` has been deprecated.\n- `panSensitivity` has been deprecated.\n- Remove `ng-content` from the gallery, use `boxTemplate` option to add your custom layer.\n- The default value for `loadingStrategy` option has changed to `LoadingStrategy.Preload`.\n- Added new dependency `bezier-easing`.\n\n\n## 7.1.2\n\n- fix(core): Fix `reserveGestureAction` input and its default value in the lightbox, in [ba95036](https://github.com/MurhafSousli/ngx-gallery/pull/481/commits/ba950362b3fd5378d929ef14b56b1cb602382c9e).\n- fix(core): Update gallery sliding position properly on window resize, in [f786d0a](https://github.com/MurhafSousli/ngx-gallery/pull/481/commits/f786d0a1ef44fd4a1c0a89126a57765131b5beba).\n- 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).\n\n## 7.1.1\n\n- 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).\n- 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).\n\n## 7.1.0\n\n- 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).\n- 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).\n- 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).\n- 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).\n- 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).\n- enhance(core, lightbox): Remove deprecated usage, in [23506eb](https://github.com/MurhafSousli/ngx-gallery/pull/472/commits/23506eb52ebd2c2e5f1d124e77dc8c8695a7eafc).\n\n## 7.0.4\n\n- fix(core): imageSize option when set to contain, in [3ecf94e](https://github.com/MurhafSousli/ngx-gallery/pull/462/commits/3ecf94e78d26378cc1330f2d432b59675526f63f).\n\n## 7.0.3\n\n- 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).\n- feat(core): Add alt property to `GalleryImage`, in [b6b5120](https://github.com/MurhafSousli/ngx-gallery/pull/460/commits/b6b512012a983699446d03481cb39f9739e1e67b).\n\n## 7.0.2\n\n- 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).\n\n## 7.0.1\n\n- feat(core): Avoid triggering change detection while dragging in [8ed5948](https://github.com/MurhafSousli/ngx-gallery/pull/456/commits/8ed5948b7e6a12624bb398ce6a70536190563778).\n- 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).\n\n## 7.0.0\n\n- Update to Angular 14 in [64d5620](https://github.com/MurhafSousli/ngx-gallery/pull/444/commits/64d5620c27ee7ea3caab8ad1cafc9eca7f0c7bf4).\n\n## 6.0.1\n\n- fix: Downgrade rxjs peerDependencies to v6 in [35f58fd](https://github.com/MurhafSousli/ngx-gallery/pull/429/commits/35f58fde087fa4f01916eb4dfd3ff6a10f9c62cc).\n\n## 6.0.0\n\n- Update to Angular 13, closes [#424](https://github.com/MurhafSousli/ngx-gallery/issues/322) in [#420](https://github.com/MurhafSousli/ngx-gallery/pull/420).\n\n## 5.1.1\n\nAdds a new option to the global config as well as an input called `thumbView` which is expects a value of either `default` or `contain`\n\n- 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).\n- feat(core): Add `stateSnapshot` property to `GalleryRef` to get an instant snapshot of the gallery state observable.\n- 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).\n- 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).\n\n > Special thanks to @NexGenUA for his PR\n\n## 5.0.0\n\n- Upgrade to Angular 10.\n- 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).\n- feat(video item): ability to disable video controls, in [f6b48b1](https://github.com/MurhafSousli/ngx-gallery/pull/326/commits/f6b48b13616d8999bc5c76bf44a6fa428191ce8b).\n- 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).\n- 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).\n- fix(lightbox): Remove cdk styles import from the library.\n- 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).\n- 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).\n\n## 5.0.0-beta.1\n\n- Upgrade to Angular 9.\n- Combine all packages in one package.\n- Remove import `HttpClientModule` from `GalleryModule`.\n\n### Breaking changes\n\n**Before:**\n\n- The packages were published on `@ngx-gallery/core`, `@ngx-gallery/lightbox` and `@ngx-gallery/gallerize`.\n\n**After:**\n\n- All the packages are now combined in `ng-gallery` (NOTE: it is not `ngx-gallery` that is a different package).\n- Import `GalleryMlodule` from `ng-gallery` and `LightboxModule` from `ng-gallery/lightbox`.\n- The module `GallerizeModule` has been removed, the `[gallerize]` directive can still be used from the `LightboxModule`.\n\n## 5.0.0-beta.0\n\n- 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).\n\n### Breaking changes (There could be more breaking changes until version 5 is released which will make it compatible with Angular 9 and ivy)\n\n- The `loadingMode` option has been removed from the gallery component's input and from the global options.\n\n## 4.0.3\n\n- 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).\n- fix(core): clean up state subject in `<gallery-image>` component in [7796b50](https://github.com/MurhafSousli/ngx-gallery/pull/283/commits/7796b500b814bd43564e2284b2a937c9d0ec2229). \n\n## 4.0.2-beta.0\n\n- 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).\n- refactor(core): refactor the if/else logic in gallery image template, in [f7d6a22](https://github.com/MurhafSousli/ngx-gallery/pull/247/commits/f7d6a22e3cc20c0977554f8d249bef6aa2076fa5).\n\n## 4.0.1\n\n- **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).\n\n## 4.0.0\n\n- **feat(gallerize):** Scan `imageSrc` and `thumbSrc` attributes for image sources, in [4826d52](https://github.com/MurhafSousli/ngx-gallery/pull/235/commits/4826d52cbaa349a4004d63f39677b436e1fe6496).\n- **enhance(core, lightbox, gallerize)**: Ability to lazy load the library.\n- **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).\n- **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).\n- **refactor(core, lightbox):** Rename `forRoot(config?)` to `withConfig(config)`, in [8446c1a](https://github.com/MurhafSousli/ngx-gallery/pull/235/commits/8446c1ae1417210698a244be6d30be81fa1eed88).\n- **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).\n\n### Breaking Changes\n\n- The function `forRoot()` has been removed from `GalleryModule` and `LightboxModule`.\n- Use `GalleryModule.withConfig({ ... })` to set config that applies on a module and its children (same applies on `LightboxModule`).\n- 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`).\n\n##### Example: Lazy load the library\n\nIn this example, will set global config without importing the library in the main bundle\n\n- Provide `GALLERY_CONFIG` value in the root module\n\n```ts\nimport { GALLERY_CONFIG } from '@ngx-gallery/core';\n\n@NgModule({\n  providers: [\n    {\n      provide: GALLERY_CONFIG, \n      useValue: {\n        dots: true,\n        imageSize: 'cover'\n      }\n    }\n  ]\n})\nexport class AppModule { }\n```\n\n- Import `GalleryModule` in a feature module\n\n```ts\nimport { GalleryModule } from '@ngx-gallery/core';\n\n@NgModule({\n  imports: [\n    GalleryModule\n  ]\n})\nexport class FeatureModule { }\n```\n\n\n## 4.0.0-beta.1\n\n- **feat(core):** Add indeterminate option to the radial progress, in [df682c4](https://github.com/MurhafSousli/ngx-gallery/pull/233/commits/df682c4353f3795dd3f45f53dfa488b428fdb99f).\n- **enhance(core):** Enhance thumbnails loading styles, in [f34f90a](https://github.com/MurhafSousli/ngx-gallery/pull/233/commits/f34f90a542aa437fa12b996dc77f6b7dd9fd819c).\n- **fix(core):** Expose `[dotSize]`, `[bulletsPosition]` and `[counterPosition]` options as inputs, in [946a856](https://github.com/MurhafSousli/ngx-gallery/pull/233/commits/946a85618acdc91692183f8f65765bbd137815cc).\n- **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).\n- **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).\n- **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).\n\n## 4.0.0-beta.0\n\n- **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).\n- **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).\n- **feat(core):** Add `dotsSize` option, in [e2e58b6](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/e2e58b62e4eb3cbcf5dd6f17417830bfc956f7eb).\n- **feat(core):** Add `counterPosition` option, closes in [ce7a8ad](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/ce7a8ad62816b3ba344a22e6e459cfdb06ad18ab).\n- **feat(core):** Use `HttpClient` to load and cache images in `[lazyImage]` directive, in [15c3e88](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/15c3e88d89434eb4860eabf3959a08ce746298e7).\n- **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).\n- **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).\n- **enhance(core):** Enhance gallery dots styles, in [de8d22b](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/de8d22bd6296d9c6a138152223ce6bffb91b6d63).\n- **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).\n- **enhance(core):** Use `animationFrameScheduler` for smoother sliding animation, in [38b0aa6](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/38b0aa67fd1b73cd69e85daab3d2b1ec69da2696).\n- **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))**\n- **refactor(core):** `[lazyImage]` directive => `(loaded)` event no longer emits on error.\n\n- **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).\n- **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).\n- **enhance(lightbox):** Import overlay default styles from `@angular/cdk/overlay`, in [54c5d88](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/54c5d88b6a3639f2066fdb7e1e7f74d307f823b6).\n- **enhance(lightbox):** Improve lightbox styles, in [4a52161](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/4a521618c8969edeb903b182de0b5ff235efe8cd).\n- **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).\n- **refactor(lightbox):** Use `disposeOnNavigation` instead of `Location` service, in [2262164](https://github.com/MurhafSousli/ngx-gallery/pull/231/commits/22621648db640905482a58480a54b693b7894272).\n\n## 3.3.1\n\n- **fix(core):** Remove duplicate delete execution in the destroyer function, in [ae541ca](https://github.com/MurhafSousli/ngx-gallery/pull/214/commits/ae541cafb22e6d5c976950eca2c7779a39693b77)\n- **fix(core):** Check galleryRef exists before deleting, in [a2b32e2](https://github.com/MurhafSousli/ngx-gallery/pull/214/commits/a2b32e23ccffb77c50c1067f1abea2977c2f1286)\n- **fix(core):** Remove duplicate config set, in [834c001](https://github.com/MurhafSousli/ngx-gallery/pull/213/commits/834c001a6cccee9955e6e9504e0a0d4cb5691d57)\n- **fix(core):** Remove unnecessary `PortalModule` import from `GalleryModule`, in [46ef735](https://github.com/MurhafSousli/ngx-gallery/pull/215/commits/46ef735cf43aa51ae026a1898b02e67b5909e520)\n- **refactor(core):** Use `povidedIn: 'root'` for the `Gallery` service, in [86eeaa7](https://github.com/MurhafSousli/ngx-gallery/pull/215/commits/86eeaa71203341a324c06a3555489a5c82b8eee9)\n- **fix(core, lightbox):** Fix peer dependencies, in [236e540](https://github.com/MurhafSousli/ngx-gallery/pull/215/commits/236e540ceaaa906aec65af0fdb99f866b5374c8f).\n\n## 3.3.0\n\n- **refactor(core):** Use `Map<string, GalleryRef>` for instances holder type instead of untyped object in [ac08077](https://github.com/MurhafSousli/ngx-gallery/commit/ac080772d7d05dd395811848cef174f31eaa622d).\n- **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).\n- **enhance(core):** Improve instance destroyer, gallery delete its instance on component destroy in [65f3358](https://github.com/MurhafSousli/ngx-gallery/commit/65f3358c035907039bf5d8199f9c14ec0e13de15).\n\n### Breaking Changes\n\n- Gallery can now be destroyed using its instance `galleryRef.destroy()`.\n- In `Gallery` service the function `destroy()` has been removed.\n\n## 3.2.0\n\n- **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).\n- **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).\n- **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).\n- **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)\n\n### Breaking changes\n\n- No need to manually import the styles anymore, they are imported internally with the components.\n- Adding a video item with multiple url sources\n  \n  **Before:**\n\n```ts\ngalleryRef.addVideo({\n  src: ['MP4_URL', 'OGG_URL'],\n  thumb: '(OPTIONAL)VIDEO_THUMBNAIL_URL',\n  poster: '(OPTIONAL)VIDEO_POSTER_URL'\n});\n```\n\n**After:**\n\n```ts\ngalleryRef.addVideo({\n  src: [\n    { url: 'MP4_URL', type: 'video/mp4' },\n    { url: 'OGG_URL', type: 'video/ogg' }\n  ],\n  thumb: '(OPTIONAL)VIDEO_THUMBNAIL_URL',\n  poster: '(OPTIONAL)VIDEO_POSTER_URL'\n});\n```\n\n## 3.1.2\n\n- **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).\n\n## 3.1.1\n\n- **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).\n- **refactor(core):** Make `contain` as the default value for `imageSize` option, in [c7b3d39](https://github.com/MurhafSousli/ngx-gallery/pull/185/commits/c7b3d39bfc257500c133cc954c8e72bc1d2ed672).\n\n## 3.1.0\n\n- **feat(core):** Add auto-play option, in [e7fc03f](https://github.com/MurhafSousli/ngx-gallery/pull/179/commits/e7fc03f3abc8c516634231ff42750806d54d22f0).\n- **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).\n- **refactor(core):** Remove opacity transition from `gallery-item`, in [a5b227e](https://github.com/MurhafSousli/ngx-gallery/pull/179/commits/a5b227eb72f9531d1884c07329b65b3c95fc0228).\n- **refactor(core):** Use `imageSize` as an attribute, in [96c5c07](https://github.com/MurhafSousli/ngx-gallery/pull/179/commits/96c5c075bf41d765b7c4667abcb374c7a6f80f1a).\n- **refactor(core):** Rename `(player)` output to `(playingChange)`, in [e209493](https://github.com/MurhafSousli/ngx-gallery/pull/179/commits/e2094937c4303fcc00f223155f2ad3d9cee29e17).\n- **enhance(core):** Use default cursor when thumbnails are disabled, in [3582e95](https://github.com/MurhafSousli/ngx-gallery/pull/179/commits/3582e95b0f3458c451f76580adda12f72220affb).\n- **fix(core):** fix vertical sliding direction, in [cba5d59](https://github.com/MurhafSousli/ngx-gallery/pull/179/commits/cba5d59074fbd37be9ee22abbef2f181364d2267).\n- **fix(core):** fix thumbClick Output, in [a730116](https://github.com/MurhafSousli/ngx-gallery/pull/179/commits/a730116e5d2154910d9a16c6a464e58b59f2e7dd).\n\n## 3.1.0-beta.0\n\n#### Gallery\n\n- **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).\n- **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).\n- **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).\n- **enhance(core):** Run HammerJS gestures outside angular zone [6fabf6c](https://github.com/MurhafSousli/ngx-gallery/commit/6fabf6ca2d421ea1cc478c1f6cd9a3b432ddd0da).\n- **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).\n- **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).\n- **fix(core):** fix wrong `(thumbClick)` emitter.\n- **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).\n- **refactor(core):** Set `loop` option to **true** by default.\n- **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).\n\n#### Lightbox\n\n- 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).\n\n\n### Breaking changes:\n\n#### Gallery\n\n- Fluid option is now used as an attribute, not as an input. \n\n**Before:**\n\n```html\n<gallery [fluid]=\"true\"></gallery>\n```\n\n**After:**\n\n```html\n<gallery fluid></gallery>\n```\n\n- Scss and css styles are put each in its own folder\n\n**Before:**\n\n```scss\n@import '~@ngx-gallery/core/styles/gallery';\n```\n\n**After:**\n\n```scss\n@import '~@ngx-gallery/core/styles/scss/gallery';\n// or for css\n@import '~@ngx-gallery/core/styles/css/gallery';\n```\n\n## 3.0.2\n\n- refactor(Lightbox): fix the close button small size on iphone browser.\n- refactor(Lightbox): use `<i>` tag instead of `<button>` tag for the close button.\n- 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).\n\n## 3.0.1\n\n- 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).\n\n## 3.0.0\n\n- 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).\n\n## 3.0.0-beta.1\n\n- 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).\n\n## 3.0.0-beta.0\n\n### Features:\n\n- Support Angular 6 and RxJS 6, closes [#91](https://github.com/MurhafSousli/ngx-gallery/issues/91).\n- feat(core): Add helper functions to add different gallery items on `<gallery>` and `GalleryRef`.\n- feat(core): Add `fluid` option to gallery for full width size.\n- feat(core): Add `navIcon` option to gallery config to set a custom nav icon.\n- 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).\n- feat(core): Add `itemClick` output which emits when an item is clicked, closes [#106](https://github.com/MurhafSousli/ngx-gallery/pull/106/files).\n- feat(core): Support custom template inside the default item templates, add `itemTemplate` and `thumbTemplate` to gallery options.\n- feat(core): Multiple video sources support.\n- feat(core): Pause Video and Youtube items when active item changes.\n- feat(Gallerize): Add support to detect Gallery component.\n- feat(Gallerize): Add support to detect DOM background images.\n\n### Bug fixes:\n\n- fix(core): Skip re-setting the config from `<gallery>` input in lightbox mode, closes [#104](https://github.com/MurhafSousli/ngx-gallery/issues/104).\n- fix(core): Fix wrong thumbnail position when `[thumbPosition]` is changed.\n\n### Improvements:\n\n- refactor(core): Improve icon rendering, use svg element instead of background-image to render the nav icon in `<gallery-nav>`.\n- refactor(core): Add `.g-active-item` on current item and `.g-active-thumb` on current thumbnail.\n- refactor(core): Add `.g-image-loaded` class on `<gallery-image>` to indicates that the image has been loaded.\n- refactor(core): Replace `loading` output with `loaded`, which emits the image path after it loads.\n- refactor(core): Set an initial height of `500px`.\n- refactor(core): Replace `ImageItem` `VideoItem` `YoutubeItem` and `IframeItem` constructor parameters with a single data parameter.\n- refactor(core, Lightbox): Set `aria-label` on all buttons.\n- refactor(Gallerize): Remove `forClass` input and replace it for `selector` input.\n- refactor(Gallerize): Remove `CommonModule` as it is not needed.\n- refactor(Styles): Add a prefix to all classes used in the plugin.\n- refactor(Styles): Add a transition for animate the opacity on current item and thumbnail.\n\n\n### Breaking changes:\n\n#### Gallery\n\n- Before, To Create an image item, we used to pass the src and the thumbnail separate parameters.\n\n```ts\nconst item: GalleryItem = new ImageItem('IMAGE_SRC', 'THUMB_SRC');\n```\n\n- After, The parameters are replaced with a single data object.\n\n```ts\nconst item: GalleryItem = new ImageItem({ src: 'IMAGE_SRC', thumb: 'THUMB_SRC' });\n```\n\n#### Gallerize\n\n- Before, Limiting auto-detection to a specific class used to be done as in the following code:\n \n```html\n<div class=\"grid\" gallerize forClass=\"my-img-class\">\n  <img class=\"my-img-class\" src=\"{{item.src}}\">\n</div>\n```\n\n- After, Now `forClass` input has been replaced with `selector` input.\n\n```html\n<div class=\"grid\" gallerize selector=\".my-img-class\">\n  <img class=\"my-img-class\" src=\"{{imgSource1}}\">\n  <div class=\"my-img-class\" [style.background]=\"'url(' + imgSource2 + ')'\">\n</div>\n```\n\n## 2.1.1\n\n- refactor(Lightbox Style): Clean up.\n- fix(HammerJS): Don't throw an error if hammer is not defined, just fallback to default.\n- feat(VideoItem): add a 3rd parameter to `VideoItem` to set custom poster.\n\n```ts\nconst viewItem = new VideoItem(video.src, video.thumb, video.poster);\n```\n\n- refactor(core): rename `thumbSrc` to `thumb`.\n\n### Breaking Changes\n\nThis won't effect the usage, but you might need to update \n\n`GalleryItem` data object has changed the name of the thumbnail source property from `thumbSrc` to `thumb`\n\nThis would only effect your app if you display the thumbnails list of your gallery items\n\nBefore\n\n```html\n<div class=\"grid\">\n  <div  class=\"grid-item\"\n        *ngFor=\"let item of galleryItems$ | async; let i = index\"\n        (click)=\"lightbox.open(i)\">\n    <img class=\"grid-image\" [src]=\"item.data.thumbSrc\">\n  </div>\n</div>\n```\n\nAfter\n\n```html\n<div class=\"grid\">\n  <div  class=\"grid-item\"\n        *ngFor=\"let item of galleryItems$ | async; let i = index\"\n        (click)=\"lightbox.open(i)\">\n    <img class=\"grid-image\" [src]=\"item.data.thumb\">\n  </div>\n</div>\n```\n\n## 2.0.4\n\n- feat(GalleryConfig): add `loadingIcon` to GalleryConfig that accepts inline image.\n\n## 2.0.3\n\n- fix(Lightbox): Exit animation, closes [#73](https://github.com/MurhafSousli/ngx-gallery/issues/73).\n- fix(Lightbox): close button is clicking behind, closes [#54](https://github.com/MurhafSousli/ngx-gallery/issues/54).\n- refactor(Lightbox): Use the button tag instead of div for close button.\n\n## 2.0.2\n\n- 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).\n\n## 2.0.1\n\n- feat(GallerySlider): Rearrange slider on window resize, closes [#67](https://github.com/MurhafSousli/ngx-gallery/issues/67).\n\n## 2.0.0\n\n- fix(Swiping): Remove ngZone, closes [#64](https://github.com/MurhafSousli/ngx-gallery/issues/64).\n\n## 2.0.0-beta.4\n\n- feat(LightboxConfig): Adds fullscreen option to the lightbox, closes [#43](https://github.com/MurhafSousli/ngx-gallery/issues/43).\n\nBy default fullscreen is obtained on small screen (mobile) but now you can make it as default for all screens\n\n```ts\nGalleryModule.forRoot()\nLightboxModule.forRoot({\n  panelClass: 'fullscreen'\n})\n```\n\n- feat(Lightbox): Ability to define lightbox config using `lightbox.open()` method\n\n```ts\nopenLightbox() {\n  this.lightbox.open(0, 'lightbox', {\n    panelClass: 'fullscreen'\n  });\n}\n```\n\n## 2.0.0-beta.3\n\n- Prevents native click event bubbling, closes [#57](https://github.com/MurhafSousli/ngx-gallery/issues/57)\n\n## 2.0.0-beta\n\n### Written from scratch\n\n## 1.0.1\n\n- fix double click on thumbnails and bullets, closes [#45](https://github.com/MurhafSousli/ng-gallery/issues/45).\n\n## 1.0.0\n\n**Fixes:**\n\n- fix(GalleryNav): Hide navigation on panning.\n- fix(GalleryPlayer): Wait until image is loaded before starting the timer.\n\n **Features:**\n- feat(GalleryPlayer): Add progressbar color option.\n- feat(GalleryPlayer): Add progressbar thickness option.\n- feat(GalleryPlayer): Add position option `top` and `bottom`.\n- feat(GalleryActions): Add gallery events\n- feat(GalleryNav): Add `prevClass` and `nextClass` options to customize navigation icons\n- feat(classNames) Add `className` option to container, thumbnails, bullets\n\n**Performance Improvements:**\n\n- refactor(GalleryThumbnail) improve performance\n\n**Breaking Changes:**\n\n- refactor(GalleryConfig): rename `config.thumbnails.space` to `config.thumbnails.margin`\n- refactor(GalleryBullets): remove vertical positioning `right` and `left`\n\n## 1.0.0-beta.8\n\n- fix(keyboard listener in lightbox) closes [#24](https://github.com/MurhafSousli/ng-gallery/issues/24), [#33](https://github.com/MurhafSousli/ng-gallery/issues/33).\n- refactor(Gallerize directive) Use MutationObserver instead of DOMSubtreeModified, closes [#26](https://github.com/MurhafSousli/ng-gallery/issues/26).\n- fix(Universal support), closes [#9](https://github.com/MurhafSousli/ng-gallery/issues/9).\n- fix Angular 5 warning, closes [#21](https://github.com/MurhafSousli/ng-gallery/issues/21).\n- Improve gallery lightbox, closes [#20](https://github.com/MurhafSousli/ng-gallery/issues/20).\n- Improve gallery lightbox slide animation, closes [#8](https://github.com/MurhafSousli/ng-gallery/issues/8).\n- Use Angular CDK for the gallery lightbox.\n- refactor(GalleryConfig)\n- Remove image transition animation option because it was not implemented properly.\n\n## 0.7.1\n\n- General refactor\n- fix(GalleryDirective) apply gallerize only once when content changes\n- decode gallery nav icons and close button from base64 to decrease the size\n\n## 0.7.0\n\n- feat(LazyLoad) emit only last selected image.\n- fix(GalleryImage) fade animation is working properly with image load.\n- refactor(GalleryConfig)\n\n## 0.6.3\n\n- fix(GalleryModal) close button is hidden on mobile, closes [#9](https://github.com/MurhafSousli/ng-gallery/issues/9)\n- fix umd bundle for systemjs, closes [#10](https://github.com/MurhafSousli/ng-gallery/issues/10)\n\n## 0.6.2\n\n- fix(gestures) remove navigation element on mobile which was blocking gestures events\n- fix(gestures) enable/disable gestures using `config.gestures`\n- refactor(config) interfaces\n\n## 0.6.0 beta\n\n- Add popup animation for gallery modal\n- Remove incorrect slide animation\n- Make gestures optional, closes [#2](https://github.com/MurhafSousli/ng-gallery/issues/2)\n- Remove thumbnail vertical position (right and left) positions, closes [#3](https://github.com/MurhafSousli/ng-gallery/issues/3)\n\n## 0.5.2 beta\n\n- (feat) gestures support\n- (refactor) gallery config \n\n## 0.5.0 beta\n\n- Initial release \n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016-2025 Murhaf Sousli\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img width=\"150px\" src=\"https://user-images.githubusercontent.com/8130692/36171173-ad0da54c-1112-11e8-85bf-843c5f70efdc.png\" style=\"max-width:100%;\">\n</p>\n<h1 align=\"center\">Angular Gallery</h1>\n\n<p align=\"center\">Simplifies the process of creating beautiful image galleries for the web and mobile devices.</p>\n\n\n[![npm](https://img.shields.io/badge/demo-online-ed1c46.svg)](https://ngx-gallery.netlify.app)\n[![npm](https://img.shields.io/badge/stackblitz-online-orange.svg)](https://stackblitz.com/edit/ngx-gallery)\n[![npm](https://img.shields.io/npm/v/ng-gallery.svg?maxAge=2592000?style=plastic)](https://www.npmjs.com/package/ng-gallery)\n[![tests](https://github.com/MurhafSousli/ngx-gallery/workflows/tests/badge.svg)](https://github.com/MurhafSousli/ngx-gallery/actions?query=workflow%3Atests)\n[![codecov](https://codecov.io/gh/MurhafSousli/ngx-gallery/graph/badge.svg?token=krc4nTzgGR)](https://codecov.io/gh/MurhafSousli/ngx-gallery)\n[![npm](https://img.shields.io/npm/dt/ng-gallery.svg?maxAge=2592000?style=plastic)](https://www.npmjs.com/package/ng-gallery)\n[![npm](https://img.shields.io/npm/l/express.svg?maxAge=2592000)](/LICENSE)\n\n\n___\n\n### Explore ngx-gallery Documentation\n\n- For **v11:** use the [wiki page](https://github.com/MurhafSousli/ngx-gallery/wiki) 📚.\n\n- For **v12:** use the [storybook documentation](https://ngx-gallery-next.netlify.app/)\n\nWe value your feedback and appreciate your support in testing this beta release!\n\n___\n\n\n## Support\n\n[![npm](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/bePatron?u=5594898)\n\n## Issues\n\nIf 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).\n\n## Author\n\n**[Murhaf Sousli](http://murhafsousli.com)**\n\n- [github/murhafsousli](https://github.com/MurhafSousli)\n- [twitter/murhafsousli](https://twitter.com/MurhafSousli)\n"
  },
  {
    "path": "angular.json",
    "content": "{\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\n  \"version\": 1,\n  \"newProjectRoot\": \"projects\",\n  \"projects\": {\n    \"ngx-gallery-demo\": {\n      \"projectType\": \"application\",\n      \"schematics\": {\n        \"@schematics/angular:component\": {\n          \"style\": \"scss\"\n        }\n      },\n      \"root\": \"projects/ng-gallery-demo\",\n      \"sourceRoot\": \"projects/ng-gallery-demo/src\",\n      \"prefix\": \"app\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:application\",\n          \"options\": {\n            \"allowedCommonJsDependencies\": [\n            ],\n            \"outputPath\": \"dist/ngx-gallery-demo\",\n            \"index\": \"projects/ng-gallery-demo/src/index.html\",\n            \"browser\": \"projects/ng-gallery-demo/src/main.ts\",\n            \"polyfills\": [\n              \"zone.js\"\n            ],\n            \"tsConfig\": \"projects/ng-gallery-demo/tsconfig.app.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              {\n                \"glob\": \"**/*\",\n                \"input\": \"projects/ng-gallery-demo/public\"\n              }\n            ],\n            \"styles\": [\n              \"projects/ng-gallery-demo/src/styles.scss\"\n            ],\n            \"scripts\": [],\n            \"server\": \"projects/ng-gallery-demo/src/main.server.ts\",\n            \"prerender\": true,\n            \"ssr\": {\n              \"entry\": \"projects/ng-gallery-demo/src/server.ts\"\n            }\n          },\n          \"configurations\": {\n            \"production\": {\n              \"budgets\": [\n                {\n                  \"type\": \"initial\",\n                  \"maximumWarning\": \"2mb\",\n                  \"maximumError\": \"3mb\"\n                },\n                {\n                  \"type\": \"anyComponentStyle\",\n                  \"maximumWarning\": \"15kb\",\n                  \"maximumError\": \"15kb\"\n                }\n              ],\n              \"outputHashing\": \"all\"\n            },\n            \"development\": {\n              \"optimization\": false,\n              \"extractLicenses\": false,\n              \"sourceMap\": true\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"serve\": {\n          \"builder\": \"@angular-devkit/build-angular:dev-server\",\n          \"configurations\": {\n            \"production\": {\n              \"buildTarget\": \"ngx-gallery-demo:build:production\"\n            },\n            \"development\": {\n              \"buildTarget\": \"ngx-gallery-demo:build:development\"\n            }\n          },\n          \"defaultConfiguration\": \"development\"\n        },\n        \"extract-i18n\": {\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\n          \"options\": {\n            \"buildTarget\": \"ngx-gallery-demo:build\"\n          }\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"polyfills\": [\n              \"zone.js\",\n              \"zone.js/testing\"\n            ],\n            \"tsConfig\": \"projects/ng-gallery-demo/tsconfig.spec.json\",\n            \"inlineStyleLanguage\": \"scss\",\n            \"assets\": [\n              {\n                \"glob\": \"**/*\",\n                \"input\": \"projects/ng-gallery-demo/public\"\n              }\n            ],\n            \"styles\": [\n              \"projects/ng-gallery-demo/src/styles.scss\"\n            ],\n            \"scripts\": []\n          }\n        }\n      }\n    },\n    \"ngx-gallery\": {\n      \"projectType\": \"library\",\n      \"root\": \"projects/ng-gallery\",\n      \"sourceRoot\": \"projects/ng-gallery/src\",\n      \"prefix\": \"lib\",\n      \"architect\": {\n        \"build\": {\n          \"builder\": \"@angular-devkit/build-angular:ng-packagr\",\n          \"options\": {\n            \"project\": \"projects/ng-gallery/ng-package.json\"\n          },\n          \"configurations\": {\n            \"production\": {\n              \"tsConfig\": \"projects/ng-gallery/tsconfig.lib.prod.json\"\n            },\n            \"development\": {\n              \"tsConfig\": \"projects/ng-gallery/tsconfig.lib.json\"\n            }\n          },\n          \"defaultConfiguration\": \"production\"\n        },\n        \"test\": {\n          \"builder\": \"@angular-devkit/build-angular:karma\",\n          \"options\": {\n            \"tsConfig\": \"projects/ng-gallery/tsconfig.spec.json\",\n            \"polyfills\": [\n              \"zone.js\",\n              \"zone.js/testing\"\n            ],\n            \"karmaConfig\": \"projects/ng-gallery/karma.conf.js\"\n          }\n        },\n        \"lint\": {\n          \"builder\": \"@angular-eslint/builder:lint\",\n          \"options\": {\n            \"lintFilePatterns\": [\n              \"projects/ng-gallery/**/*.ts\",\n              \"projects/ng-gallery/**/*.html\"\n            ]\n          }\n        }\n      }\n    }\n  },\n  \"cli\": {\n    \"schematicCollections\": [\n      \"@angular-eslint/schematics\"\n    ]\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"ng-gallery-project\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"start-demo\": \"ng serve ngx-gallery-demo --host 0.0.0.0\",\n    \"build-demo\": \"ng build ngx-gallery-demo --configuration production\",\n    \"build-lib\": \"ng build ngx-gallery --configuration production\",\n    \"lint-lib\": \"ng lint ngx-gallery\",\n    \"test-lib\": \"ng test ngx-gallery\",\n    \"test-lib-headless\": \"ng test ngx-gallery --watch=false --no-progress --browsers=ChromeHeadless --code-coverage\",\n    \"publish-lib\": \"npm publish ./dist/ng-gallery\",\n    \"storybook\": \"ng run ngx-gallery:storybook\",\n    \"build-storybook\": \"ng run ngx-gallery:build-storybook\",\n    \"init-msw\": \"msw init src/\",\n    \"chromatic\": \"npx chromatic --project-token=chpt_4359317e034d873\",\n    \"serve:ssr:ng-gallery-demo\": \"node dist/ngx-gallery-demo/server/server.mjs\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/MurhafSousli/ngx-gallery.git\"\n  },\n  \"dependencies\": {\n    \"@angular/animations\": \"^19.1.5\",\n    \"@angular/cdk\": \"^19.1.3\",\n    \"@angular/common\": \"^19.1.5\",\n    \"@angular/compiler\": \"^19.1.5\",\n    \"@angular/core\": \"^19.1.5\",\n    \"@angular/forms\": \"^19.1.5\",\n    \"@angular/material\": \"^19.1.3\",\n    \"@angular/platform-browser\": \"^19.1.5\",\n    \"@angular/platform-browser-dynamic\": \"^19.1.5\",\n    \"@angular/platform-server\": \"^19.1.5\",\n    \"@angular/router\": \"^19.1.5\",\n    \"@angular/ssr\": \"^19.0.6\",\n    \"@fortawesome/angular-fontawesome\": \"^1.0.0\",\n    \"@fortawesome/fontawesome-svg-core\": \"^6.7.2\",\n    \"@fortawesome/free-brands-svg-icons\": \"^6.7.2\",\n    \"@fortawesome/free-solid-svg-icons\": \"^6.7.2\",\n    \"hammerjs\": \"^2.0.8\",\n    \"highlight.js\": \"^11.11.1\",\n    \"ngx-highlightjs\": \"^14.0.0\",\n    \"ngx-progressbar\": \"^14.0.0\",\n    \"ngx-scrollbar\": \"^18.0.0\",\n    \"rxjs\": \"~7.8.1\",\n    \"tslib\": \"^2.8.1\",\n    \"zone.js\": \"^0.15.0\"\n  },\n  \"devDependencies\": {\n    \"@angular-devkit/build-angular\": \"^19.1.6\",\n    \"@angular-eslint/builder\": \"19.1.0\",\n    \"@angular-eslint/eslint-plugin\": \"19.1.0\",\n    \"@angular-eslint/eslint-plugin-template\": \"19.1.0\",\n    \"@angular-eslint/schematics\": \"19.1.0\",\n    \"@angular-eslint/template-parser\": \"19.1.0\",\n    \"@angular/cli\": \"^19.1.6\",\n    \"@angular/compiler-cli\": \"^19.1.5\",\n    \"@types/express\": \"^4.17.17\",\n    \"@types/jasmine\": \"~5.1.5\",\n    \"@types/node\": \"^22.10.3\",\n    \"@typescript-eslint/eslint-plugin\": \"^8.24.0\",\n    \"@typescript-eslint/parser\": \"^8.24.0\",\n    \"@typescript-eslint/types\": \"^8.24.0\",\n    \"@typescript-eslint/utils\": \"^8.24.0\",\n    \"eslint\": \"^9.17.0\",\n    \"express\": \"^4.18.2\",\n    \"jasmine-core\": \"^5.5.0\",\n    \"karma\": \"~6.4.4\",\n    \"karma-chrome-launcher\": \"~3.2.0\",\n    \"karma-coverage\": \"~2.2.0\",\n    \"karma-jasmine\": \"~5.1.0\",\n    \"karma-jasmine-html-reporter\": \"~2.1.0\",\n    \"ng-packagr\": \"^19.1.2\",\n    \"typescript\": \"~5.5.4\"\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/.eslintrc.json",
    "content": "{\n  \"root\": true,\n  \"ignorePatterns\": [\n    \"projects/**/*\"\n  ],\n  \"overrides\": [\n    {\n      \"files\": [\n        \"*.ts\"\n      ],\n      \"extends\": [\n        \"eslint:recommended\",\n        \"plugin:@typescript-eslint/recommended\",\n        \"plugin:@angular-eslint/recommended\",\n        \"plugin:@angular-eslint/template/process-inline-templates\"\n      ],\n      \"rules\": {\n        \"@angular-eslint/directive-selector\": [\n          \"error\",\n          {\n            \"type\": \"attribute\",\n            \"prefix\": \"\",\n            \"style\": \"camelCase\"\n          }\n        ],\n        \"@angular-eslint/component-selector\": [\n          \"error\",\n          {\n            \"type\": \"element\",\n            \"prefix\": \"\",\n            \"style\": \"kebab-case\"\n          }\n        ],\n        \"@angular-eslint/component-class-suffix\": 0,\n        \"@angular-eslint/directive-class-suffix\": 0,\n        \"@angular-eslint/no-input-rename\": 0,\n        \"@angular-eslint/no-output-rename\": 0,\n        \"@angular-eslint/no-output-native\": 0,\n        \"@angular-eslint/no-host-metadata-property\": 0\n      }\n    },\n    {\n      \"files\": [\n        \"*.html\"\n      ],\n      \"extends\": [\n        \"plugin:@angular-eslint/template/recommended\",\n        \"plugin:@angular-eslint/template/accessibility\"\n      ],\n      \"rules\": {\n        \"@angular-eslint/template/elements-content\": 0\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "projects/ng-gallery/.storybook/colors.mdx",
    "content": "{/* Colors.mdx */}\n\nimport { Meta, ColorPalette, ColorItem } from '@storybook/blocks';\n\n<Meta title=\"Colors\" />\n\n<ColorPalette>\n  <ColorItem\n    title=\"theme.color.greyscale\"\n    subtitle=\"Some of the greys\"\n    colors={{ White: '#FFFFFF', Alabaster: '#F8F8F8', Concrete: '#F3F3F3' }}\n  />\n  <ColorItem\n    title=\"theme.color.primary\"\n    subtitle=\"Coral\"\n    colors={{ WildWatermelon: '#FF4785' }}\n  />\n  <ColorItem\n    title=\"theme.color.secondary\"\n    subtitle=\"Ocean\"\n    colors={{ DodgerBlue: '#1e5dfd' }}\n  />\n  <ColorItem\n    title=\"theme.color.positive\"\n    subtitle=\"Green\"\n    colors={{\n      Apple: 'rgba(102,191,60,1)',\n      Apple80: 'rgba(102,191,60,.8)',\n      Apple60: 'rgba(102,191,60,.6)',\n      Apple30: 'rgba(102,191,60,.3)',\n    }}\n  />\n</ColorPalette>\n"
  },
  {
    "path": "projects/ng-gallery/.storybook/main.ts",
    "content": "import type { StorybookConfig } from \"@storybook/angular\";\n\nconst config: StorybookConfig = {\n  stories: [\"../src/**/*.mdx\", \"../src/**/*.stories.@(js|jsx|mjs|ts|tsx)\"],\n  addons: [\n    \"@storybook/addon-links\",\n    \"@storybook/addon-essentials\",\n    \"@storybook/addon-interactions\"\n  ],\n  framework: {\n    name: \"@storybook/angular\",\n    options: {},\n  },\n  docs: {}\n};\nexport default config;\n"
  },
  {
    "path": "projects/ng-gallery/.storybook/manager.js",
    "content": "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",
    "content": "import type { Preview } from \"@storybook/angular\";\nimport { setCompodocJson } from \"@storybook/addon-docs/angular\";\n\nimport docJson from \"../documentation.json\";\nsetCompodocJson(docJson);\n\nconst preview: Preview = {\n  parameters: {\n    layout: 'fullscreen',\n    actions: { argTypesRegex: \"^on[A-Z].*\" },\n    controls: {\n      matchers: {\n        color: /(background|color)$/i,\n        date: /Date$/i,\n      }\n    }\n  },\n};\n\nexport default preview;\n"
  },
  {
    "path": "projects/ng-gallery/.storybook/theme.js",
    "content": "import { create } from '@storybook/theming/create';\n\nexport default create({\n  base: 'dark',\n  // Typography\n  fontBase: '\"Open Sans\", sans-serif',\n  fontCode: 'monospace',\n\n  brandTitle: 'Angular Gallery',\n  brandUrl: 'https://github.com/MurhafSousli/ngx-gallery',\n  brandImage: 'https://github.com/MurhafSousli/ngx-gallery/assets/8130692/f9a4a981-7a61-4f3d-9e00-06ec3b2efd1c',\n  brandTarget: '_self',\n\n  //\n  colorPrimary: '#3a95ff',\n  colorSecondary: '#c2c6ce',\n\n  // UI\n  // appBg: '#ffffff',\n  appContentBg: '#797979',\n  // appBorderColor: '#585C6D',\n  // appBorderRadius: 4,\n\n  // Text colors\n  // textColor: '#10162F',\n  // textInverseColor: '#ffffff',\n\n  // Toolbar default and active colors\n  // barTextColor: '#9E9E9E',\n  // barSelectedColor: '#585C6D',\n  // barBg: '#ffffff',\n\n  // Form colors\n  // inputBg: '#ffffff',\n  // inputBorder: '#10162F',\n  // inputTextColor: '#10162F',\n  // inputBorderRadius: 2,\n});\n"
  },
  {
    "path": "projects/ng-gallery/.storybook/tsconfig.json",
    "content": "{\n  \"extends\": \"../tsconfig.lib.json\",\n  \"compilerOptions\": {\n    \"types\": [\"node\"],\n    \"allowSyntheticDefaultImports\": true,\n    \"resolveJsonModule\": true\n  },\n  \"exclude\": [\"../src/test.ts\", \"../src/**/*.spec.ts\"],\n  \"include\": [\"../src/**/*\", \"./preview.ts\"],\n  \"files\": [\"./typings.d.ts\"]\n}\n"
  },
  {
    "path": "projects/ng-gallery/.storybook/typings.d.ts",
    "content": "declare module '*.md' {\n  const content: string;\n  export default content;\n}\n"
  },
  {
    "path": "projects/ng-gallery/README.md",
    "content": "<p align=\"center\">\n  <img width=\"150px\" src=\"https://user-images.githubusercontent.com/8130692/36171173-ad0da54c-1112-11e8-85bf-843c5f70efdc.png\" style=\"max-width:100%;\">\n</p>\n<h1 align=\"center\">Angular Gallery</h1>\n\n<p align=\"center\">Simplifies the process of creating beautiful image galleries for the web and mobile devices.</p>\n\n\n[![npm](https://img.shields.io/badge/demo-online-ed1c46.svg)](https://ngx-gallery.netlify.app)\n[![npm](https://img.shields.io/badge/stackblitz-online-orange.svg)](https://stackblitz.com/edit/ngx-gallery)\n[![npm](https://img.shields.io/npm/v/ng-gallery.svg?maxAge=2592000?style=plastic)](https://www.npmjs.com/package/ng-gallery)\n[![tests](https://github.com/MurhafSousli/ngx-gallery/workflows/tests/badge.svg)](https://github.com/MurhafSousli/ngx-gallery/actions?query=workflow%3Atests)\n[![npm](https://img.shields.io/npm/dt/ng-gallery.svg?maxAge=2592000?style=plastic)](https://www.npmjs.com/package/ng-gallery)\n[![npm](https://img.shields.io/npm/l/express.svg?maxAge=2592000)](/LICENSE)\n\n\n___\n\n### Explore ngx-gallery Documentation\n\n- For **v11:** use the [wiki page](https://github.com/MurhafSousli/ngx-gallery/wiki) 📚.\n\n- For **v12:** use the [storybook documentation](https://ngx-gallery-next.netlify.app/)\n\nWe value your feedback and appreciate your support in testing this beta release!\n\n___\n\n\n## Support\n\n[![npm](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/bePatron?u=5594898)\n\n## Issues\n\nIf 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).\n\n## Author\n\n**[Murhaf Sousli](http://murhafsousli.com)**\n\n- [github/murhafsousli](https://github.com/MurhafSousli)\n- [twitter/murhafsousli](https://twitter.com/MurhafSousli)\n"
  },
  {
    "path": "projects/ng-gallery/karma.conf.js",
    "content": "// Karma configuration file, see link for more information\n// https://karma-runner.github.io/1.0/config/configuration-file.html\n\nmodule.exports = function (config) {\n  config.set({\n    basePath: '',\n    frameworks: ['jasmine', '@angular-devkit/build-angular'],\n    plugins: [\n      require('karma-jasmine'),\n      require('karma-chrome-launcher'),\n      require('karma-jasmine-html-reporter'),\n      require('karma-coverage'),\n      require('@angular-devkit/build-angular/plugins/karma')\n    ],\n    client: {\n      jasmine: {\n        // you can add configuration options for Jasmine here\n        // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html\n        // for example, you can disable the random execution with `random: false`\n        // or set a specific seed with `seed: 4321`\n      },\n      clearContext: false // leave Jasmine Spec Runner output visible in browser\n    },\n    jasmineHtmlReporter: {\n      suppressAll: true // removes the duplicated traces\n    },\n    coverageReporter: {\n      dir: require('path').join(__dirname, '../../coverage/ng-gallery'),\n      subdir: '.',\n      reporters: [\n        { type: 'html' },\n        { type: 'text-summary' },\n        { type: 'cobertura' },\n        { type: 'lcov' }\n      ]\n    },\n    reporters: ['progress', 'kjhtml'],\n    browsers: [\"MyChromeWithoutSearchSelect\"],\n    customLaunchers: {\n      MyChromeWithoutSearchSelect: {\n        base: \"Chrome\",\n        flags: [\"-disable-search-engine-choice-screen\"],\n      },\n    },\n    restartOnFileChange: true\n  });\n};\n"
  },
  {
    "path": "projects/ng-gallery/lightbox/ng-package.json",
    "content": "{}\n"
  },
  {
    "path": "projects/ng-gallery/lightbox/src/gallerize.directive.ts",
    "content": "import {\n  Directive,\n  Input,\n  OnInit,\n  OnDestroy,\n  Inject,\n  Optional,\n  Self,\n  Host,\n  NgZone,\n  ElementRef,\n} from '@angular/core';\nimport { DOCUMENT } from '@angular/common';\nimport { GalleryRef, GalleryComponent, GalleryItemData } from 'ng-gallery';\nimport { Subject, Subscription, from, map, switchMap, finalize, debounceTime, EMPTY } from 'rxjs';\n\nimport { Lightbox } from './lightbox.service';\n\n/**\n * This directive has 2 modes:\n * 1 - If host element is a HTMLElement, it detects the images and hooks their clicks to lightbox\n * 2 - If host element is a GalleryComponent, it hooks the images click to the lightbox\n */\n\nconst enum GallerizeMode {\n  Detector = 'detector',\n  Gallery = 'gallery'\n}\n\n@Directive({\n  selector: '[gallerize]',\n  standalone: true\n})\nexport class GallerizeDirective implements OnInit, OnDestroy {\n\n  /** Default gallery id */\n  private _galleryId: string = 'lightbox';\n\n  /** Gallerize mode */\n  private readonly _mode: GallerizeMode;\n\n  /** If host element is a HTMLElement, will use the following variables: */\n\n  /** Stream that emits to fire the detection stream the image elements has changed */\n  private _observer$: MutationObserver;\n\n  /** Stream that emits when image is discover */\n  private _detector$: Subject<void>;\n\n  /** If host element is a GalleryComponent, will use the following variables: */\n\n  /** Gallery events (if used on a gallery component) */\n  private _itemClick$: Subscription;\n  private _itemChange$: Subscription;\n\n  // ======================================================\n\n  /** If set, it will become the gallery id */\n  @Input() gallerize: string;\n\n  /** The selector used to query images elements */\n  @Input() selector: string = 'img';\n\n  constructor(private _zone: NgZone,\n              private _el: ElementRef,\n              // private _gallery: Gallery,\n              private _lightbox: Lightbox,\n              @Inject(DOCUMENT) private _document: Document,\n              @Host() @Self() @Optional() private _galleryCmp: GalleryComponent) {\n\n    // Set gallerize mode\n    this._mode = _galleryCmp ? GallerizeMode.Gallery : GallerizeMode.Detector;\n  }\n\n  ngOnInit(): void {\n    this._zone.runOutsideAngular(() => {\n      this._galleryId = this.gallerize || this._galleryId;\n      // const ref: GalleryRef = this._gallery.ref(this._galleryId);\n\n      // switch (this._mode) {\n      //   case GallerizeMode.Detector:\n      //     this.detectorMode(ref);\n      //     break;\n      //   case GallerizeMode.Gallery:\n      //     this.galleryMode(ref);\n      // }\n    });\n  }\n\n  ngOnDestroy(): void {\n    switch (this._mode) {\n      case GallerizeMode.Detector:\n        this._detector$.complete();\n        this._observer$.disconnect();\n        break;\n      case GallerizeMode.Gallery:\n        this._itemClick$.unsubscribe();\n        this._itemChange$.unsubscribe();\n    }\n  }\n\n  /** Gallery mode: means `gallerize` directive is used on `<gallery>` component\n   * Adds a click event to each gallery item so it opens in lightbox */\n  private galleryMode(galleryRef: GalleryRef): void {\n    // Clone its items to the new gallery instance\n    this._itemClick$ = this._galleryCmp.galleryRef.itemClick.subscribe((i: number) => this._lightbox.open(i, this._galleryId));\n    this._itemChange$ = this._galleryCmp.galleryRef.itemsChanged.subscribe(() => galleryRef.load(this._galleryCmp.galleryRef.items()));\n  }\n\n  /** Detector mode: means `gallerize` directive is used on a normal HTMLElement\n   *  Detects images and adds a click event to each image, so it opens in the lightbox */\n  private detectorMode(galleryRef: GalleryRef): void {\n    this._detector$ = new Subject();\n    // Query image elements\n    this._detector$.pipe(\n      debounceTime(300),\n      switchMap(() => {\n\n        /** get all img elements from content */\n        const imageElements = this._el.nativeElement.querySelectorAll(this.selector);\n\n        if (imageElements && imageElements.length) {\n\n          const images: GalleryItemData[] = [];\n\n          return from(imageElements).pipe(\n            map((el: HTMLElement, i: number) => {\n              // Add click event to the image\n              el.style.cursor = 'pointer';\n              el.addEventListener('click', () => {\n                this._zone.run(() => this._lightbox.open(i, this._galleryId));\n              });\n\n              if (el instanceof HTMLImageElement) {\n                // If element is type of img use the src property\n                return {\n                  src: el.getAttribute('imageSrc') || el.src,\n                  thumb: el.getAttribute('thumbSrc') || el.src\n                };\n              } else {\n                // Otherwise, use element background-image url\n                const elStyle = this._document.defaultView.getComputedStyle(el, null);\n                const background = elStyle.backgroundImage.slice(4, -1).replace(/\"/g, '');\n                return {\n                  src: el.getAttribute('imageSrc') || background,\n                  thumb: el.getAttribute('thumbSrc') || background\n                };\n              }\n            }),\n            // tap((data: any) => images.push(new ImageItem(data))),\n            finalize(() => galleryRef.load(images))\n          );\n        } else {\n          return EMPTY;\n        }\n      })\n    ).subscribe();\n\n    // Observe content changes\n    this._observer$ = new MutationObserver(() => this._detector$.next());\n    this._observer$.observe(this._el.nativeElement, { childList: true, subtree: true });\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/lightbox/src/lightbox.animation.ts",
    "content": "import { animate, state, style, transition, trigger } from '@angular/animations';\n\nexport const lightboxAnimation = trigger('lightbox', [\n  state('void, exit', style({ opacity: 0, transform: 'scale(0.7)' })),\n  state('enter', style({ transform: 'none' })),\n  transition('* => enter', animate('{{startAnimationTime}}ms cubic-bezier(0, 0, 0.2, 1)',\n    style({ transform: 'none', opacity: 1 }))),\n  transition('* => void, * => exit',\n    animate('{{exitAnimationTime}}ms cubic-bezier(0.4, 0.0, 0.2, 1)', style({ opacity: 0 }))),\n]);\n"
  },
  {
    "path": "projects/ng-gallery/lightbox/src/lightbox.component.scss",
    "content": "@mixin fullscreen() {\n  width: 100%;\n\n  gallery {\n    max-width: unset;\n    max-height: unset;\n    //position: fixed;\n    //top: 0;\n    //left: 0;\n    //bottom: 0;\n    //right: 0;\n    height: 100%;\n    width: 100%;\n    border-radius: 0;\n  }\n}\n\n:host {\n  display: contents;\n\n  ::ng-deep {\n    gallery {\n      position: relative;\n      display: block;\n      width: 1100px;\n      //height: 800px;\n      max-width: 94vw;\n      max-height: 90vh;\n      margin: 0;\n    }\n  }\n}\n\ndialog {\n  border: none;\n  padding: 0;\n  border-radius: 8px;\n  overflow: hidden;\n  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);\n  max-width: 80%;\n  animation: vanish 400ms cubic-bezier(0, 0, 0.2, 1);\n\n  &::backdrop {\n    animation: backdropVanish 400ms cubic-bezier(0, 0, 0.2, 1);\n    background-image: linear-gradient(\n        45deg,\n        magenta,\n        rebeccapurple,\n        dodgerblue,\n        green\n    );\n    opacity: 0.75;\n  }\n\n  &[open] {\n    opacity: 1;\n    animation: appear 400ms cubic-bezier(0, 0, 0.2, 1);\n\n    &::backdrop {\n      animation: backdropAppear 400ms cubic-bezier(0, 0, 0.2, 1);\n    }\n  }\n}\n\n@keyframes appear {\n  from {\n    opacity: 0;\n    scale: 0.7;\n  }\n  to {\n    opacity: 1;\n    scale: 1;\n  }\n}\n\n@keyframes vanish {\n  from {\n    display: block;\n    opacity: 1;\n    scale: 1;\n  }\n  to {\n    display: none;\n    opacity: 0;\n    scale: 0.7;\n  }\n}\n\n@keyframes backdropAppear {\n  from {\n    opacity: 0;\n  }\n  to {\n    opacity: 0.75;\n  }\n}\n\n@keyframes backdropVanish {\n  from {\n    display: block;\n    opacity: 0.75;\n  }\n  to {\n    display: none;\n    opacity: 0;\n  }\n}\n\n.g-backdrop {\n  background-color: rgba(0, 0, 0, .32);\n}\n\n.fullscreen {\n  @include fullscreen();\n}\n\n.g-overlay {\n  margin: auto;\n\n  @media only screen and (max-width: 480px) {\n    @include fullscreen();\n  }\n}\n\n.g-btn-close {\n  position: absolute;\n  right: 0.9em;\n  top: 0.9em;\n  z-index: 60;\n  //cursor: pointer;\n  //width: 20px;\n  //height: 20px;\n  //@media only screen and (max-width: 480px) {\n  //  right: 0.7em;\n  //  top: 0.7em;\n  //}\n\n  //svg {\n  //  width: 100%;\n  //  height: 100%;\n  //  opacity: 0.6;\n  //  transition: opacity linear 150ms;\n  //  filter: drop-shadow(0px 0px 2px rgba(0, 0, 0, 0.8));\n  //\n  //  &:hover {\n  //    opacity: 1;\n  //  }\n  //}\n}\n"
  },
  {
    "path": "projects/ng-gallery/lightbox/src/lightbox.component.ts",
    "content": "import { Component, viewChild, contentChild, Signal, ElementRef, ChangeDetectionStrategy } from '@angular/core';\nimport { GalleryComponent } from 'ng-gallery';\n\n@Component({\n  selector: 'lightbox',\n  template: `\n    <dialog #dialogElement>\n      <button class=\"g-btn-close\" aria-label=\"Close\" (click)=\"close()\">Close</button>\n\n      <ng-content select=\"gallery\"/>\n    </dialog>\n  `,\n  styleUrl: './lightbox.component.scss',\n  changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class LightboxComponent {\n\n  dialog: Signal<ElementRef<HTMLDialogElement>> = viewChild('dialogElement');\n\n  gallery: Signal<GalleryComponent> = contentChild(GalleryComponent);\n\n  showModal(i: number): void {\n    this.gallery().set(i, 'auto');\n    this.dialog().nativeElement.showModal();\n  }\n\n  close(): void {\n    this.dialog().nativeElement.close();\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/lightbox/src/lightbox.default.ts",
    "content": "import { LightboxConfig } from './lightbox.model';\n\nexport const defaultConfig: LightboxConfig = {\n  backdropClass: 'g-backdrop',\n  panelClass: 'g-overlay',\n  hasBackdrop: true,\n  keyboardShortcuts: true,\n  role: 'lightbox',\n  startAnimationTime: 150,\n  exitAnimationTime: 75,\n  closeIcon: `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<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\">\n\t<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\"/>\n</svg>\n`\n};\n"
  },
  {
    "path": "projects/ng-gallery/lightbox/src/lightbox.directive.ts",
    "content": "// import { Directive, Input, ElementRef, OnInit, OnDestroy, Renderer2 } from '@angular/core';\n// import { fromEvent, tap, SubscriptionLike, Subscription } from 'rxjs';\n// import { Lightbox } from './lightbox.service';\n//\n// @Directive({\n//   selector: '[lightbox]'\n// })\n// export class LightboxDirective implements OnInit, OnDestroy {\n//\n//   clickEvent: SubscriptionLike = Subscription.EMPTY;\n//\n//   @Input('lightbox') index = 0;\n//   @Input('gallery') id = 'root';\n//\n//   constructor(private _lightbox: Lightbox, private _el: ElementRef, private _renderer: Renderer2) {\n//   }\n//\n//   ngOnInit() {\n//     // this._renderer.setStyle(this._el.nativeElement, 'cursor', 'pointer');\n//     // this.clickEvent = fromEvent(this._el.nativeElement, 'click').pipe(\n//     //   tap(() => this._lightbox.open(this.index, this.id))\n//     // ).subscribe();\n//   }\n//\n//   ngOnDestroy() {\n//     this.clickEvent.unsubscribe();\n//   }\n// }\n"
  },
  {
    "path": "projects/ng-gallery/lightbox/src/lightbox.model.ts",
    "content": "import { InjectionToken, Provider } from '@angular/core';\nimport { defaultConfig } from './lightbox.default';\n\nexport const LIGHTBOX_CONFIG: InjectionToken<LightboxConfig> = new InjectionToken<LightboxConfig>('LIGHTBOX_CONFIG', {\n  providedIn: 'root',\n  factory: () => defaultConfig\n});\n\nexport interface LightboxConfig {\n  backdropClass?: string | string[];\n  panelClass?: string | string[];\n  hasBackdrop?: boolean;\n  keyboardShortcuts?: boolean;\n  closeIcon?: string;\n  role?: string;\n  ariaLabelledBy?: string;\n  ariaLabel?: string;\n  ariaDescribedBy?: string;\n  startAnimationTime?: number;\n  exitAnimationTime?: number;\n}\n\nexport function provideLightboxOptions(options: LightboxConfig): Provider {\n  return {\n    provide: LIGHTBOX_CONFIG,\n    useValue: { ...defaultConfig, ...options }\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/lightbox/src/lightbox.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { GalleryModule } from 'ng-gallery';\n// import { LightboxDirective } from './lightbox.directive';\nimport { GallerizeDirective } from './gallerize.directive';\n\n@NgModule({\n  imports: [\n    GalleryModule,\n    // LightboxDirective,\n    GallerizeDirective\n  ],\n  exports: [\n    GalleryModule,\n    // LightboxDirective,\n    GallerizeDirective\n  ]\n})\nexport class LightboxModule {\n}\n"
  },
  {
    "path": "projects/ng-gallery/lightbox/src/lightbox.service.ts",
    "content": "import { ComponentRef, inject, Injectable } from '@angular/core';\nimport { DomSanitizer } from '@angular/platform-browser';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { Overlay, OverlayRef, OverlayConfig } from '@angular/cdk/overlay';\nimport { LEFT_ARROW, RIGHT_ARROW, ESCAPE } from '@angular/cdk/keycodes';\n// import { Gallery } from 'ng-gallery';\nimport { Subject } from 'rxjs';\n\nimport { LightboxConfig, LIGHTBOX_CONFIG } from './lightbox.model';\nimport { LightboxComponent } from './lightbox.component';\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class Lightbox {\n\n  /** Gallery overlay ref */\n  private _overlayRef: OverlayRef;\n\n  // private _gallery: Gallery = inject(Gallery);\n\n  private _overlay: Overlay = inject(Overlay);\n\n  private _sanitizer: DomSanitizer = inject(DomSanitizer);\n\n  /** Global config */\n  private _config: LightboxConfig = inject(LIGHTBOX_CONFIG);\n\n  /** Stream that emits when lightbox is opened */\n  opened: Subject<string> = new Subject<string>();\n\n  /** Stream that emits when lightbox is closed */\n  closed: Subject<string> = new Subject<string>();\n\n  /**\n   * Set Lightbox Config\n   * @param config - LightboxConfig\n   */\n  setConfig(config: LightboxConfig) {\n    this._config = { ...this._config, ...config };\n  }\n\n  /**\n   * Open Lightbox Overlay\n   * @param i - Current Index\n   * @param id - Gallery ID\n   * @param config - Lightbox Config\n   */\n  open(i = 0, id = 'lightbox', config?: LightboxConfig) {\n\n    const _config: LightboxConfig = config ? { ...this._config, ...config } : this._config;\n\n    const overlayConfig: OverlayConfig = {\n      backdropClass: _config.backdropClass,\n      panelClass: _config.panelClass,\n      hasBackdrop: _config.hasBackdrop,\n      positionStrategy: this._overlay.position().global().centerHorizontally().centerVertically(),\n      scrollStrategy: this._overlay.scrollStrategies.block(),\n      disposeOnNavigation: true\n    };\n\n    // const galleryRef = this._gallery.ref(id);\n    // galleryRef.set(i);\n\n    this._overlayRef = this._overlay.create(overlayConfig);\n\n    // overlay opened event\n    this._overlayRef.attachments().subscribe(() => this.opened.next(id));\n\n    // overlay closed event\n    this._overlayRef.detachments().subscribe(() => this.closed.next(id));\n\n    // Attach gallery to the overlay\n    const galleryPortal = new ComponentPortal(LightboxComponent);\n    const lightboxRef: ComponentRef<LightboxComponent> = this._overlayRef.attach(galleryPortal);\n\n    if (_config.hasBackdrop) {\n      this._overlayRef.backdropClick().subscribe(() => this.close());\n    }\n\n    // Add keyboard shortcuts\n    if (_config.keyboardShortcuts) {\n      this._overlayRef.keydownEvents().subscribe((event: any) => {\n        switch (event.keyCode) {\n          case LEFT_ARROW:\n            // galleryRef.prev();\n            break;\n          case RIGHT_ARROW:\n            // galleryRef.next();\n            break;\n          case ESCAPE:\n            this.close();\n        }\n      });\n    }\n  }\n\n  /**\n   * Close Lightbox Overlay\n   */\n  close() {\n    if (this._overlayRef.hasAttached()) {\n      this._overlayRef.detach();\n    }\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/lightbox/src/public_api.ts",
    "content": "export * from './lightbox.model';\nexport * from './lightbox.component';\nexport * from './lightbox.service';\nexport * from './gallerize.directive';\n// export * from './lightbox.directive';\nexport * from './lightbox.module';\n"
  },
  {
    "path": "projects/ng-gallery/ng-package.json",
    "content": "{\n  \"$schema\": \"../../node_modules/ng-packagr/ng-package.schema.json\",\n  \"dest\": \"../../dist/ng-gallery\",\n  \"lib\": {\n    \"entryFile\": \"src/public-api.ts\"\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/package.json",
    "content": "{\n  \"name\": \"ng-gallery\",\n  \"version\": \"13.0.0\",\n  \"homepage\": \"https://ngx-gallery.netlify.app/\",\n  \"author\": {\n    \"name\": \"Murhaf Sousli\",\n    \"url\": \"http://github.com/murhafsousli\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/murhafsousli/ngx-gallery.git\"\n  },\n  \"bugs\": {\n    \"url\": \"http://github.com/murhafsousli/ngx-gallery/issues\"\n  },\n  \"keywords\": [\n    \"gallery\",\n    \"slider\",\n    \"carousel\",\n    \"lightbox\",\n    \"images\",\n    \"video\",\n    \"modal\"\n  ],\n  \"license\": \"MIT\",\n  \"peerDependencies\": {\n    \"@angular/common\": \">=19.0.0\",\n    \"@angular/core\": \">=19.0.0\",\n    \"@angular/cdk\": \">=19.0.0\",\n    \"rxjs\": \">=7.0.0\"\n  },\n  \"dependencies\": {\n    \"tslib\": \"^2.3.1\"\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/auto-height/auto-height.spec.ts",
    "content": "// import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\n// import { NoopAnimationsModule } from '@angular/platform-browser/animations';\n// import { By } from '@angular/platform-browser';\n// import { DebugElement } from '@angular/core';\n// import { GalleryRef } from 'ng-gallery';\n// import { getObservableFromContext, TestComponent } from './common';\n// import { filter, firstValueFrom, fromEvent, Observable } from 'rxjs';\n// import { AutoHeight } from '../observers/auto-height';\n// import { ImgManager } from '../utils/img-manager';\n//\n// fdescribe('Auto-height directive', () => {\n//   let fixture: ComponentFixture<TestComponent>;\n//   let autoHeightDirective: AutoHeight;\n//   let galleryRef: GalleryRef;\n//   let manager: ImgManager;\n//   let autoHeightDebugElement: DebugElement;\n//\n//   beforeEach(() => {\n//     TestBed.configureTestingModule({\n//       imports: [\n//         NoopAnimationsModule,\n//         TestComponent\n//       ],\n//       providers: [\n//         { provide: ComponentFixtureAutoDetect, useValue: true }\n//       ]\n//     }).compileComponents();\n//\n//     fixture = TestBed.createComponent(TestComponent);\n//     autoHeightDebugElement = fixture.debugElement.query(By.directive(AutoHeight));\n//     autoHeightDirective = autoHeightDebugElement.injector.get(AutoHeight);\n//     galleryRef = autoHeightDebugElement.injector.get(GalleryRef);\n//     manager = autoHeightDebugElement.injector.get(ImgManager);\n//     fixture.detectChanges();\n//   });\n//\n//   it('should create [autoHeight] directive', () => {\n//     expect(autoHeightDirective).toBeTruthy();\n//   });\n//\n//   fit('should observe when items become visible as soon as possible', async () => {\n//     TestBed.flushEffects();\n//     await firstValueFrom(galleryRef.afterItemsVisible);\n//\n//     galleryRef.next('smooth');\n//\n//     const transitionEnd$ = fromEvent(autoHeightDebugElement.nativeElement, 'transitionend');\n//\n//     expect(autoHeightDirective.isResizing()).toBeTrue();\n//\n//     // const img: HTMLImageElement = await firstValueFrom(manager.getActiveItem());\n//     // const el: HTMLElement = autoHeightDebugElement.nativeElement;\n//     //\n//     // await firstValueFrom(transitionEnd$);\n//     // expect(autoHeightDirective.isResizing()).toBeFalse();\n//     // expect(el.parentElement.parentElement.parentElement.clientHeight).toBe(img.naturalHeight);\n//   });\n// });\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/auto-height/auto-height.ts",
    "content": "// import {\n//   Directive,\n//   effect,\n//   inject,\n//   signal,\n//   untracked,\n//   WritableSignal,\n//   EffectCleanupRegisterFn\n// } from '@angular/core';\n// import {\n//   Observable,\n//   Subscription,\n//   of,\n//   filter,\n//   fromEvent,\n//   switchMap,\n//   tap,\n//   take,\n//   debounceTime,\n//   animationFrameScheduler\n// } from 'rxjs';\n// import { ImgManager } from '../utils/img-manager';\n// import { GalleryComponent } from '../core/gallery.component';\n// import { ResizeSensor } from '../services/resize-sensor';\n//\n// /**\n//  * Auto height feature prerequisites:\n//  * - autosize should be set to 'false'\n//  * - if thumbnails exist, it should not be aligned to the right or left\n//  */\n//\n// @Directive({\n//   selector: 'gallery[autoHeight]',\n//   host: {\n//     '[attr.autoHeight]': 'true',\n//     '[class.g-resizing]': 'isResizing()',\n//     '[style.--slider-auto-height.px]': 'height()',\n//   }\n// })\n// export class AutoHeight {\n//\n//   private readonly gallery: GalleryComponent = inject(GalleryComponent);\n//\n//   private readonly manager: ImgManager = inject(ImgManager);\n//\n//   readonly isResizing: WritableSignal<boolean> = signal(false);\n//\n//   readonly height: WritableSignal<number> = signal(0);\n//\n//   constructor() {\n//     let sub$: Subscription;\n//\n//     let afterHeightChanged$: Observable<any>;\n//\n//     effect((onCleanup: EffectCleanupRegisterFn) => {\n//       const resizeSensor: ResizeSensor = this.gallery.slider().resizeSensor();\n//       // Check if height has transition for the auto-height feature\n//       const transitionDuration: string = getComputedStyle(resizeSensor.nativeElement).transitionDuration;\n//       if (!parseFloat(transitionDuration)) {\n//         afterHeightChanged$ = of({});\n//       } else {\n//         console.log(transitionDuration)\n//         afterHeightChanged$ = fromEvent(resizeSensor.nativeElement, 'transitionend');\n//       }\n//       // if (!this.galleryRef.config().autoHeight) return;\n//       // const currIndex = this.galleryRef.currIndex();\n//       untracked(() => {\n//         sub$ = this.manager.getActiveItem().pipe(\n//           filter((img: HTMLImageElement) => !!img),\n//           // Wait for item image to be rendered\n//           debounceTime(0, animationFrameScheduler),\n//           // Skip if img height is equal the slider height\n//           filter((img: HTMLImageElement) => {\n//             console.log('🦕', resizeSensor.nativeElement.clientHeight, img.height)\n//             return img.height !== resizeSensor.nativeElement.clientHeight\n//           }),\n//           switchMap((img: HTMLImageElement) => {\n//             console.log('👽 Resize started! --slider-height', resizeSensor.nativeElement.clientHeight, img.height)\n//             resizeSensor.disabled.set(true);\n//             this.isResizing.set(true);\n//\n//             resizeSensor.nativeElement.style.setProperty('--slider-height', `${img.height}px`)\n//\n//             return afterHeightChanged$.pipe(\n//               debounceTime(0, animationFrameScheduler),\n//               tap(() => {\n//                 resizeSensor.disabled.set(false);\n//                 this.isResizing.set(false);\n//               }),\n//               take(1)\n//             );\n//           })\n//         ).subscribe();\n//\n//         onCleanup(() => sub$?.unsubscribe());\n//       });\n//     });\n//   }\n// }\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/autoplay/autoplay.directive.ts",
    "content": "// import {\n//   Directive,\n//   inject,\n//   effect,\n//   untracked,\n//   EffectCleanupRegisterFn\n// } from '@angular/core';\n// import { Subscription, delay, of, switchMap, tap } from 'rxjs';\n// import { ImgManager } from '../utils/img-manager';\n// import { GalleryRef } from '../services/gallery-ref';\n// import { GalleryConfig } from '../models/config.model';\n//\n// @Directive({\n//   selector: 'gallery[autoplay]'\n// })\n// export class AutoplayDirective {\n//\n//   private _galleryRef: GalleryRef = inject(GalleryRef);\n//\n//   private _imgManager: ImgManager = inject(ImgManager);\n//\n//   constructor() {\n//     let sub: Subscription;\n//\n//     // TODO: Should not observe config in the two effects, will be refactored\n//     // TODO: Make especial inputs for the autoplay directive such as autoplayScrollBehavior\n//\n//     // effect((onCleanup: EffectCleanupRegisterFn) => {\n//     //   const config: GalleryConfig = this._galleryRef.config();\n//     //   const isPlaying: boolean = this._galleryRef.isPlaying();\n//     //\n//     //   untracked(() => {\n//     //     if (isPlaying) {\n//     //       sub = this._imgManager.getActiveItem().pipe(\n//     //         switchMap(() => of({}).pipe(\n//     //           delay(config.autoplayInterval),\n//     //           tap(() => {\n//     //             if (this._galleryRef.hasNext()) {\n//     //               this._galleryRef.next(config.scrollBehavior);\n//     //             } else {\n//     //               this._galleryRef.set(0, config.scrollBehavior);\n//     //             }\n//     //           })\n//     //         ))\n//     //       ).subscribe();\n//     //     }\n//     //     onCleanup(() => sub?.unsubscribe());\n//     //   });\n//     // });\n//\n//     effect(() => {\n//       const autoplay: boolean = this._galleryRef.config().autoplay;\n//       untracked(() => autoplay ? this._galleryRef.play() : this._galleryRef.stop());\n//     });\n//   }\n// }\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/bullets/gallery-bullets.component.ts",
    "content": "import {\n  Component,\n  inject,\n  numberAttribute,\n  booleanAttribute,\n  input,\n  InputSignal,\n  ChangeDetectionStrategy,\n  InputSignalWithTransform\n} from '@angular/core';\nimport { GalleryRef } from '../services/gallery-ref';\nimport { HorizontalPosition } from '../models/config.model';\n\n@Component({\n  selector: 'gallery-bullets',\n  host: {\n    '[attr.align]': 'align()',\n    '[attr.disabled]': 'disabled()'\n  },\n  template: `\n    @for (item of galleryRef.items(); track i; let i = $index) {\n      <div class=\"g-bullet\"\n           [class.g-bullet-active]=\"i === galleryRef.currIndex()\"\n           [style.width.px]=\"size()\"\n           [style.height.px]=\"size()\"\n           (click)=\"disabled() ? null : galleryRef.set(i, scrollBehavior())\">\n        <div class=\"g-bullet-inner\"></div>\n      </div>\n    }\n  `,\n  styleUrl: './gallery-bullets.scss',\n  changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class GalleryBulletsComponent {\n\n  readonly galleryRef: GalleryRef = inject(GalleryRef);\n\n  readonly scrollBehavior: InputSignal<ScrollBehavior> = input<ScrollBehavior>('smooth');\n\n  /**\n   * Align bullets\n   */\n  readonly align: InputSignal<HorizontalPosition> = input<HorizontalPosition>('top');\n\n  /**\n   * Disables thumbnails' clicks\n   */\n  readonly disabled: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(false, {\n    transform: booleanAttribute\n  });\n\n  /**\n   * Disables thumbnails' clicks\n   */\n  readonly size: InputSignalWithTransform<number, string | number> = input<number, string | number>(6, {\n    transform: numberAttribute\n  });\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/bullets/gallery-bullets.scss",
    "content": ":host {\n  position: absolute;\n  left: 50%;\n  z-index: 99;\n  transform: translateX(-50%);\n  display: flex;\n  gap: 6px;\n  top: var(--bullets-top);\n  bottom: var(--bullets-bottom);\n\n  // Gallery bullets variables\n  --bullets-top: unset;\n  --bullets-bottom: unset;\n  --bullets-cursor: pointer;\n  --bullets-opacity: 0.4;\n  --bullets-hover-opacity: 1;\n  --bullets-active-opacity: 1;\n\n  &[align='top'] {\n    --bullets-top: 15px;\n  }\n\n  &[align='bottom'] {\n    --bullets-bottom: 15px;\n  }\n\n  &[disabled='true'] {\n    --bullets-cursor: default;\n    --bullets-hover-opacity: var(--bullets-opacity);\n  }\n}\n\n:host,\n.g-bullet,\n.g-bullet-inner {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n}\n\n.g-bullet {\n  cursor: var(--bullets-cursor);\n  z-index: 20;\n\n  &:hover .g-bullet-inner {\n    opacity: var(--bullets-hover-opacity);\n  }\n}\n\n.g-bullet-active .g-bullet-inner {\n  opacity: var(--bullets-active-opacity);\n}\n\n.g-bullet-inner {\n  background-color: var(--g-overlay-color);\n  opacity: var(--bullets-opacity);\n  width: 100%;\n  height: 100%;\n  border-radius: 50%;\n  transition: opacity linear 150ms;\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/bullets/gallery-bullets.spec.ts",
    "content": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { Component, DebugElement, Signal, viewChild } from '@angular/core';\nimport { By } from '@angular/platform-browser';\nimport {\n  GalleryBulletsComponent,\n  GalleryComponent,\n  GalleryItemData,\n  GalleryItemDef,\n  GalleryRef,\n  ImgRecognizer\n} from 'ng-gallery';\nimport { img1, img2, img3 } from '../tests/test-images';\n\n@Component({\n  imports: [GalleryComponent, GalleryBulletsComponent, GalleryItemDef, ImgRecognizer],\n  template: `\n    <gallery [items]=\"items\" [style.width.px]=\"width\" [style.height.px]=\"height\">\n      <img *galleryItemDef=\"let item\"\n           galleryImage\n           [src]=\"item.src\"/>\n\n      <gallery-bullets [align]=\"align\" [disabled]=\"disabled\" [size]=\"size\" [scrollBehavior]=\"scrollBehavior\"/>\n    </gallery>\n  `\n})\nexport class TestComponent {\n  items: GalleryItemData[] = [\n    { src: img1 },\n    { src: img2 },\n    { src: img3 }\n  ];\n  width: number = 500;\n  height: number = 300;\n\n  align: 'top' | 'bottom' = 'top';\n  disabled: boolean = false;\n  size: number = 6;\n  scrollBehavior: ScrollBehavior = 'smooth';\n\n  gallery: Signal<GalleryComponent> = viewChild(GalleryComponent);\n}\n\ndescribe('Gallery bullets component', () => {\n  let fixture: ComponentFixture<TestComponent>;\n  let component: TestComponent;\n  let bulletsComponent: GalleryBulletsComponent;\n  let galleryRef: GalleryRef;\n  let bulletsComponentElement: DebugElement;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [\n        NoopAnimationsModule,\n        TestComponent\n      ],\n      providers: [\n        { provide: ComponentFixtureAutoDetect, useValue: true }\n      ]\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(TestComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n\n    bulletsComponentElement = fixture.debugElement.query(By.directive(GalleryBulletsComponent));\n    bulletsComponent = bulletsComponentElement.injector.get(GalleryBulletsComponent);\n    galleryRef = bulletsComponentElement.injector.get(GalleryRef);\n  });\n\n  it('should create gallery-bullets component', () => {\n    expect(bulletsComponent).toBeTruthy();\n    expect(galleryRef).toBeTruthy();\n  });\n\n  it('should set the align attribute', () => {\n    expect(bulletsComponent.align()).toBe('top');\n    expect((bulletsComponentElement.nativeElement as HTMLElement).getAttribute('align')).toBe('top');\n\n    // Change attribute value\n    component.align = 'bottom';\n    fixture.detectChanges();\n\n    expect(bulletsComponent.align()).toBe('bottom');\n    expect((bulletsComponentElement.nativeElement as HTMLElement).getAttribute('align')).toBe('bottom');\n  });\n\n  it('should set the disabled attribute', () => {\n    expect(bulletsComponent.disabled()).toBeFalse();\n    expect((bulletsComponentElement.nativeElement as HTMLElement).getAttribute('disabled')).toBe('false');\n\n    // Change attribute value\n    component.disabled = true;\n    fixture.detectChanges();\n\n    expect(bulletsComponent.disabled()).toBeTrue();\n    expect((bulletsComponentElement.nativeElement as HTMLElement).getAttribute('disabled')).toBe('true');\n  });\n\n  it('should set the size input', () => {\n    expect(bulletsComponent.size()).toBe(6);\n\n    // Change size value\n    component.size = 10;\n    fixture.detectChanges();\n\n    expect(bulletsComponent.size()).toBe(10);\n  });\n\n  it('should set the scrollBehavior input', () => {\n    expect(bulletsComponent.scrollBehavior()).toBe('smooth');\n\n    // Change size value\n    component.scrollBehavior = 'auto';\n    fixture.detectChanges();\n\n    expect(bulletsComponent.scrollBehavior()).toBe('auto');\n  });\n});\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/core/gallery.component.ts",
    "content": "import {\n  Component,\n  inject,\n  output,\n  booleanAttribute,\n  numberAttribute,\n  computed,\n  effect,\n  untracked,\n  input,\n  viewChild,\n  contentChild,\n  Signal,\n  InputSignal,\n  TemplateRef,\n  OutputEmitterRef,\n  ChangeDetectionStrategy,\n  InputSignalWithTransform\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { Directionality } from '@angular/cdk/bidi';\nimport { GalleryRef } from '../services/gallery-ref';\nimport { GALLERY_CONFIG, GalleryConfig, Orientation } from '../models/config.model';\nimport { BezierEasingOptions } from '../smooth-scroll';\nimport { GalleryItemContext, GalleryItemDef } from '../directives/gallery-item-def.directive';\nimport { GalleryBoxDef, GalleryStateContext } from '../directives/gallery-box-def.directive';\nimport { ImgManager } from '../utils/img-manager';\n// import { AutoplayDirective } from '../autoplay/autoplay.directive';\nimport { GallerySliderComponent } from '../slider/gallery-slider.component';\nimport { GalleryItemData } from '../templates/items.model';\n\n/**\n * Gallery component\n */\n@Component({\n  selector: 'gallery',\n  host: {\n    '[attr.dir]': 'dir.value',\n    '[attr.debug]': 'debug()',\n    '[attr.imageSize]': 'imageSize()',\n    '[attr.orientation]': 'orientation()',\n    '[attr.itemAutosize]': 'itemAutosize()',\n    '[attr.scrollDisabled]': 'disableScroll()'\n  },\n  template: `\n    <ng-content select=\"gallery-thumbs, gallery-bullets\"/>\n\n    <div class=\"g-box\">\n\n      <gallery-slider [class.g-debug]=\"debug()\"\n                      [template]=\"itemTemplate()\"\n                      (itemClick)=\"itemClick.emit($event)\">\n        <ng-content select=\"gallery-nav, gallery-counter\"/>\n      </gallery-slider>\n\n      <div class=\"g-box-template\">\n        <!--        <ng-container *ngTemplateOutlet=\"boxTemplate(); context: { state: state(), config: config() }\"/>-->\n        <ng-container *ngTemplateOutlet=\"boxTemplate(); context: { config: config() }\"/>\n      </div>\n    </div>\n  `,\n  styleUrls: ['./gallery.scss', '../debug/debug.scss'],\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  // hostDirectives: [AutoplayDirective],\n  imports: [GallerySliderComponent, NgTemplateOutlet],\n  providers: [ImgManager, GalleryRef]\n})\nexport class GalleryComponent {\n\n  slider: Signal<GallerySliderComponent> = viewChild(GallerySliderComponent);\n\n  /**\n   * The gallery reference instance\n   */\n  readonly galleryRef: GalleryRef = inject(GalleryRef);\n\n\n  readonly dir: Directionality = inject(Directionality);\n\n  /**\n   * @ignore\n   */\n  private _config: GalleryConfig = inject(GALLERY_CONFIG);\n\n  /**\n   * Loads the items array into the gallery\n   */\n  items: InputSignal<GalleryItemData[]> = input<GalleryItemData[]>();\n\n  /**\n   * Enables loop cycling\n   */\n  loop: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(this._config.loop, {\n    transform: booleanAttribute\n  });\n\n  /**\n   * Show visuals that helps debugging the component\n   */\n  debug: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(this._config.debug, {\n    transform: booleanAttribute\n  });\n\n  /**\n   * Centralize slider\n   */\n  centralized: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(this._config.centralized, {\n    transform: booleanAttribute\n  });\n\n  /**\n   * Fits each item size to its content, This option should be used with:\n   * - Does not work if `autoHeight` is turned on\n   * - Does not work properly unless `loadingAttr=\"eager\"`\n   * - Does not work properly unless `loadingStrategy=\"preload\"`\n   */\n  itemAutosize: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(this._config.itemAutosize, {\n    transform: booleanAttribute\n  });\n\n  /**\n   * Automatically cycle through items at time interval\n   */\n  autoplay: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(this._config.autoplay, {\n    transform: booleanAttribute\n  });\n\n  /**\n   * Disables sliding using mousewheel, touchpad, scroll and gestures on touch devices\n   */\n  disableScroll: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(this._config.disableScroll, {\n    transform: booleanAttribute\n  });\n\n  /**\n   * Disables sliding using the mouse\n   */\n  disableMouseScroll: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(this._config.disableMouseScroll, {\n    transform: booleanAttribute\n  });\n\n  /**\n   * Sets the interval used for the autoplay feature\n   */\n  autoplayInterval: InputSignalWithTransform<number, string | number> = input<number, string | number>(this._config.autoplayInterval, {\n    transform: numberAttribute\n  });\n\n  /**\n   * Sets the duration used for smooth navigation between the items\n   */\n  scrollDuration: InputSignalWithTransform<number, string | number> = input<number, string | number>(this._config.scrollDuration, {\n    transform: numberAttribute\n  });\n\n  /**\n   * Sets the debounce time used to throttle the gallery update after it is resized\n   */\n  resizeDebounceTime: InputSignalWithTransform<number, string | number> = input<number, string | number>(this._config.resizeDebounceTime, {\n    transform: numberAttribute\n  });\n\n  /**\n   * Sets the scroll behavior when the active item is changed\n   */\n  scrollBehavior: InputSignal<ScrollBehavior> = input<ScrollBehavior>(this._config.scrollBehavior);\n\n  /**\n   * Sets the ease function used for smooth navigation between the items\n   */\n  scrollEase: InputSignal<BezierEasingOptions> = input<BezierEasingOptions>(this._config.scrollEase);\n\n  /**\n   * Sets the object-fit style applied on items' images\n   */\n  imageSize: InputSignal<'cover' | 'contain'> = input<'cover' | 'contain'>(this._config.imageSize);\n\n  /**\n   * Sets the sliding direction\n   */\n  orientation: InputSignal<Orientation> = input<Orientation>(this._config.orientation);\n\n  /**\n   * Skip initializing the config with components inputs (Lightbox mode)\n   * This intended to be used and enabled from the lightbox component\n   * @ignore\n   */\n  skipInitConfig: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(false, {\n    transform: booleanAttribute\n  });\n\n  /**\n   * Stream that emits when an item is clicked\n   */\n  itemClick: OutputEmitterRef<number> = output<number>();\n\n  /**\n   * Stream that emits when a thumbnail is clicked\n   */\n  thumbClick: OutputEmitterRef<number> = output<number>();\n\n  /**\n   * Stream that emits when player state is changed\n   */\n  // playingChange: OutputEmitterRef<GalleryState> = output<GalleryState>();\n\n  /**\n   * Stream that emits when index is changed\n   */\n  // indexChange: OutputEmitterRef<GalleryState> = output<GalleryState>();\n\n  /**\n   * Stream that emits when items array is changed\n   */\n  // itemsChange: OutputEmitterRef<GalleryState> = output<GalleryState>();\n\n  /** @ignore */\n  private _galleryItemDef: Signal<GalleryItemDef> = contentChild(GalleryItemDef);\n  /** @ignore */\n  private _galleryBoxDef: Signal<GalleryBoxDef> = contentChild(GalleryBoxDef);\n\n  itemTemplate: Signal<TemplateRef<GalleryItemContext<GalleryItemData>>> = computed(() => this._galleryItemDef()?.templateRef)\n  boxTemplate: Signal<TemplateRef<GalleryStateContext>> = computed(() => this._galleryBoxDef()?.templateRef)\n\n  /** @ignore */\n  config: Signal<GalleryConfig> = computed(() => {\n    return {\n      loop: this.loop(),\n      debug: this.debug(),\n      autoplay: this.autoplay(),\n      imageSize: this.imageSize(),\n      centralized: this.centralized(),\n      scrollBehavior: this.scrollBehavior(),\n      scrollEase: this.scrollEase(),\n      autoplayInterval: this.autoplayInterval(),\n      scrollDuration: this.scrollDuration(),\n      orientation: this.orientation(),\n      resizeDebounceTime: this.resizeDebounceTime(),\n      disableScroll: this.disableScroll(),\n      disableMouseScroll: this.disableMouseScroll(),\n      itemAutosize: this.itemAutosize()\n    };\n  });\n\n  constructor() {\n    effect(() => {\n      const config: GalleryConfig = this.config();\n      untracked(() => this.galleryRef.setConfig(config));\n    });\n\n    effect(() => {\n      const items: GalleryItemData[] = this.items();\n      untracked(() => this.galleryRef.load(items));\n    });\n  }\n\n  /**\n   * Go to next item\n   */\n  next(behavior?: ScrollBehavior, loop?: boolean): void {\n    this.galleryRef.next(behavior, loop);\n  }\n\n  /**\n   * Go to prev item\n   */\n  prev(behavior?: ScrollBehavior, loop?: boolean): void {\n    this.galleryRef.prev(behavior, loop);\n  }\n\n  /**\n   * Set active item\n   */\n  set(i: number, behavior?: ScrollBehavior): void {\n    this.galleryRef.set(i, behavior);\n  }\n\n  /**\n   * Reset to initial state\n   */\n  reset(): void {\n    this.galleryRef.reset();\n  }\n\n  /**\n   * Start the player\n   */\n  play(interval?: number): void {\n    this.galleryRef.play(interval);\n  }\n\n  /**\n   * Stop the player\n   */\n  stop(): void {\n    this.galleryRef.stop();\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/core/gallery.scss",
    "content": ":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 rgba(0, 0, 0, 0.6));\n  --g-box-shadow: 0 0 3px rgba(0, 0, 0, 0.6);\n  --g-font-color: #000;\n  --g-overlay-color: #fff;\n  --g-gutter-size: 0px;\n  //--g-gutter-size: 1px;\n\n  z-index: 1;\n  position: relative;\n  overflow: hidden;\n\n  gap: var(--g-gutter-size);\n  width: 100%;\n  //width: 776px;\n  height: 500px;\n  min-height: 100%;\n  max-height: 100%;\n  background-color: black;\n\n  display: grid;\n  grid-template-columns: auto minmax(0, 1fr) auto;\n  grid-template-rows: auto minmax(0, 1fr) auto;\n  grid-template-areas:\n    \". top .\"\n    \"left center right\"\n    \". bottom .\";\n\n\n  // Gallery items variables\n  --g-item-width: 100%;\n  --g-item-height: 100%;\n  --g-item-max-height: var(--slider-height);\n\n  &.g-resizing {\n    // Changes the height of the slider to match the active item height\n    //--slider-height: var(--slider-auto-height) !important;\n    //::ng-deep {\n    //  .g-slider {\n    //    height: var(--slider-auto-height) !important;\n    //  }\n    //}\n  }\n\n  &[gallerize] {\n    --g-item-cursor: pointer;\n  }\n\n  // Gallery auto-height variables\n  &[autoHeight='true'][itemAutoSize='false'][orientation='horizontal'] {\n    //&[thumbPosition='top'], &[thumbPosition='bottom'] {\n      // if auto-height, use fit-content\n      height: fit-content;\n      --g-item-height: auto !important;\n      --g-item-max-height: auto;\n    //}\n  }\n\n  // Gallery image variables\n  --image-object-fit: unset;\n\n  &[imageSize='contain'] gallery-slider  {\n    --image-object-fit: contain;\n  }\n\n  &[imageSize='cover'] gallery-slider  {\n    --image-object-fit: cover;\n  }\n\n  // Gallery thumbs variables\n  --slider-thumb-height: unset;\n  --slider-thumb-width: unset;\n  --thumb-slider-left: unset;\n  --thumb-slider-overflow: unset;\n  --thumb-slider-flex-direction: unset;\n  --g-thumb-width: unset;\n  --g-thumb-height: unset;\n  --g-thumb-cursor: pointer;\n\n  // Gallery slider variables\n  --slider-scroll-snap-type: unset;\n  --slider-overflow: unset;\n  --slider-flex-direction: unset;\n  --slider-width: unset;\n  --slider-height: unset;\n  --slider-content-width: unset;\n  --slider-content-height: unset;\n\n  &[orientation='horizontal'] {\n    --slider-overflow: auto hidden;\n    --slider-scroll-snap-type: x mandatory;\n    --slider-flex-direction: row;\n    --slider-content-height: 100%;\n  }\n\n  &[orientation='vertical'] {\n    --slider-overflow: hidden auto;\n    --slider-scroll-snap-type: y mandatory;\n    --slider-flex-direction: column;\n    --slider-content-width: 100%;\n  }\n\n  &[scrollDisabled='true'] {\n    --slider-overflow: hidden !important;\n  }\n\n}\n\n.g-box {\n  grid-area: center;\n  overflow: hidden;\n  position: relative;\n  display: flex;\n  flex-direction: column;\n}\n\n.g-box-template {\n  position: absolute;\n  z-index: 10;\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/core/gallery.spec.ts",
    "content": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { DebugElement } from '@angular/core';\nimport { By } from '@angular/platform-browser';\nimport { provideNoopAnimations } from '@angular/platform-browser/animations';\nimport { SliderItem } from '../slider/slider-item/slider-item';\nimport { TestComponent } from '../tests/common';\nimport { GalleryRef } from 'ng-gallery';\n\n\ndescribe('Gallery component', () => {\n  let fixture: ComponentFixture<TestComponent>;\n  let component: TestComponent;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [TestComponent],\n      providers: [\n        provideNoopAnimations(),\n        { provide: ComponentFixtureAutoDetect, useValue: true }\n      ]\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(TestComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create gallery', () => {\n    expect(component.gallery()).toBeTruthy();\n  });\n\n  it('should load and render items in the gallery', () => {\n    expect(component.gallery().galleryRef.items()).toBe(component.items);\n    const items: DebugElement[] = fixture.debugElement.queryAll(By.directive(SliderItem));\n    expect(items.length).toBe(3);\n  });\n\n  it('should trigger forward galleryRef functions', () => {\n    const galleryRef: GalleryRef = component.gallery().galleryRef;\n\n    spyOn(component.gallery().galleryRef, 'next');\n    component.gallery().next('auto', true);\n    expect(galleryRef.next).toHaveBeenCalledOnceWith('auto', true);\n\n    spyOn(component.gallery().galleryRef, 'prev');\n    component.gallery().prev('auto', true);\n    expect(galleryRef.prev).toHaveBeenCalledOnceWith('auto', true);\n\n    spyOn(component.gallery().galleryRef, 'set');\n    component.gallery().set(5, 'auto');\n    expect(galleryRef.set).toHaveBeenCalledOnceWith(5, 'auto');\n\n    spyOn(component.gallery().galleryRef, 'reset');\n    component.gallery().reset();\n    expect(galleryRef.reset).toHaveBeenCalled();\n\n    spyOn(component.gallery().galleryRef, 'play');\n    component.gallery().play(3000);\n    expect(galleryRef.play).toHaveBeenCalledOnceWith(3000);\n\n    spyOn(component.gallery().galleryRef, 'stop');\n    component.gallery().stop();\n    expect(galleryRef.stop).toHaveBeenCalled();\n  });\n});\n\n// it('should trigger pan event', () => {\n//   // Find the element\n//   const pannableElement = fixture.debugElement.query(By.css('.pannable')).nativeElement;\n//\n//   // Create a mock Pan event\n//   const panEvent = new Event('pan');\n//   Object.assign(panEvent, {\n//     deltaX: 100, // Pan distance in X axis\n//     deltaY: 0,   // Pan distance in Y axis\n//     type: 'pan',\n//   });\n//\n//   // Dispatch the event\n//   pannableElement.dispatchEvent(panEvent);\n//\n//   // Assert the expected behavior\n//   expect(component.panEventTriggered).toBeTrue();\n// });\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/counter/gallery-counter.component.ts",
    "content": "import {\n  Component,\n  inject,\n  computed,\n  input,\n  Signal,\n  InputSignal,\n  ChangeDetectionStrategy\n} from '@angular/core';\nimport { GalleryRef } from '../services/gallery-ref';\nimport { HorizontalPosition } from '../models/config.model';\n\n@Component({\n  selector: 'gallery-counter',\n  host: {\n    '[attr.align]': 'align()'\n  },\n  template: `\n    <div class=\"g-counter\">{{ counter() }}</div>\n  `,\n  styleUrl: './gallery-counter.scss',\n  changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class GalleryCounterComponent {\n\n  readonly galleryRef: GalleryRef = inject(GalleryRef);\n\n  readonly align: InputSignal<HorizontalPosition> = input<HorizontalPosition>('top');\n\n  readonly counter: Signal<string> = computed(() => {\n    return `${ this.galleryRef.currIndex() + 1 } / ${ this.galleryRef.items().length }`;\n  });\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/counter/gallery-counter.scss",
    "content": ":host {\n  // Gallery position variables\n  --counter-top: unset;\n  --counter-bottom: unset;\n  --counter-border-radius: unset;\n\n  &[align='top'] {\n    --counter-top: 0;\n    --counter-border-radius: 0 0 4px 4px;\n  }\n\n  &[align='bottom'] {\n    --counter-bottom: 0;\n    --counter-border-radius: 4px 4px 0 0;\n  }\n}\n\n.g-counter {\n  font-weight: bold;\n  user-select: none;\n  opacity: 0.6;\n  transition: opacity linear 150ms;\n  z-index: 50;\n  position: absolute;\n  left: 50%;\n  transform: translateX(-50%) perspective(1px);\n  font-size: 12px;\n  padding: 4px 10px;\n  color: var(--g-font-color);\n  background-color: var(--g-overlay-color);\n  box-shadow: var(--g-box-shadow);\n\n  top: var(--counter-top);\n  bottom: var(--counter-bottom);\n  border-radius: var(--counter-border-radius);\n\n  &:hover {\n    opacity: 0.8;\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/counter/gallery-counter.spec.ts",
    "content": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { Component, DebugElement, Signal, viewChild } from '@angular/core';\nimport { By } from '@angular/platform-browser';\nimport { firstValueFrom } from 'rxjs';\nimport {\n  GalleryComponent,\n  GalleryCounterComponent,\n  GalleryItemData,\n  GalleryItemDef,\n  GalleryRef,\n  ImgRecognizer\n} from 'ng-gallery';\nimport { img1, img2, img3 } from '../tests/test-images';\nimport { afterTimeout } from '../tests/common';\n\n@Component({\n  imports: [GalleryComponent, GalleryCounterComponent, GalleryItemDef, ImgRecognizer],\n  template: `\n    <gallery [items]=\"items\" [style.width.px]=\"width\" [style.height.px]=\"height\">\n      <img *galleryItemDef=\"let item\"\n           galleryImage\n           [src]=\"item.src\"/>\n\n      <gallery-counter [align]=\"align\"/>\n    </gallery>\n  `\n})\nexport class TestComponent {\n  items: GalleryItemData[] = [\n    { src: img1 },\n    { src: img2 },\n    { src: img3 }\n  ];\n  width: number = 500;\n  height: number = 300;\n\n  align: 'top' | 'bottom' = 'top';\n\n  gallery: Signal<GalleryComponent> = viewChild(GalleryComponent);\n}\n\ndescribe('Gallery counter component', () => {\n  let fixture: ComponentFixture<TestComponent>;\n  let component: TestComponent;\n  let counterComponent: GalleryCounterComponent;\n  let galleryRef: GalleryRef;\n  let counterComponentElement: DebugElement;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [\n        NoopAnimationsModule,\n        TestComponent\n      ],\n      providers: [\n        { provide: ComponentFixtureAutoDetect, useValue: true }\n      ]\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(TestComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n\n    counterComponentElement = fixture.debugElement.query(By.directive(GalleryCounterComponent));\n    counterComponent = counterComponentElement.injector.get(GalleryCounterComponent);\n    galleryRef = counterComponentElement.injector.get(GalleryRef);\n  });\n\n  it('should create gallery-counter component', () => {\n    expect(counterComponent).toBeTruthy();\n    expect(galleryRef).toBeTruthy();\n  });\n\n  it('should set the align attribute', () => {\n    expect(counterComponent.align()).toBe('top');\n    expect((counterComponentElement.nativeElement as HTMLElement).getAttribute('align')).toBe('top');\n\n    // Change attribute value\n    component.align = 'bottom';\n    fixture.detectChanges();\n\n    expect(counterComponent.align()).toBe('bottom');\n    expect((counterComponentElement.nativeElement as HTMLElement).getAttribute('align')).toBe('bottom');\n  });\n\n  it('should calculate counter based on current index and total number of items', async () => {\n    await firstValueFrom(galleryRef.afterItemsVisible);\n    expect(counterComponent.counter()).toBe('1 / 3');\n\n    component.gallery().next('auto');\n    await afterTimeout(100);\n    expect(counterComponent.counter()).toBe('2 / 3');\n  });\n});\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/debug/debug.scss",
    "content": ":host[debug='true'] {\n  ::ng-deep {\n    .g-sliding, .g-resizing, .g-scrolling {\n      gallery-item.g-item-highlight {\n        //visibility: hidden;\n      }\n    }\n  }\n\n  ::ng-deep {\n    gallery-slider {\n      &:after, &:before {\n        position: absolute;\n        content: '';\n        z-index: 12;\n      }\n\n      &:before {\n        width: 100%;\n        height: 0;\n        border-top: 1px dashed lime;\n      }\n\n      &:after {\n        height: 100%;\n        width: 0;\n        border-left: 1px dashed lime;\n      }\n\n      gallery-item {\n        //outline: 1px solid darkorange;\n\n        &.g-item-highlight {\n          &:after {\n            content: '';\n            position: absolute;\n            width: 100%;\n            height: 100%;\n            border: 3px solid lime;\n            box-sizing: border-box;\n            z-index: 10;\n          }\n        }\n      }\n    }\n\n    .g-sliding {\n      .g-slider-sliding {\n        display: block;\n      }\n    }\n\n    .g-scrolling {\n      .g-slider-scrolling {\n        display: block;\n      }\n    }\n\n    .g-resizing {\n      .g-slider-resizing {\n        display: block;\n      }\n    }\n\n    .g-slider-observed {\n      display: block !important;\n    }\n\n    .g-slider-debug {\n      position: absolute;\n      top: 0;\n      left: 0;\n      display: flex;\n      gap: 5px;\n      padding: 10px;\n\n      .g-slider-resizing {\n        background: rgba(245, 76, 40);\n      }\n\n      .g-slider-scrolling {\n        background: rgb(255, 133, 36);\n      }\n\n      .g-slider-sliding {\n        background: rgb(31, 108, 185);\n      }\n\n      .g-slider-observed {\n        background: rgb(31, 185, 139);\n      }\n\n      div, &:before {\n        display: none;\n        color: white;\n        font-family: monospace;\n        z-index: 12;\n        padding: 2px 6px;\n        border-radius: 3px;\n      }\n    }\n  }\n\n  &[itemAutoSize='false'] {\n    ::ng-deep {\n      .g-slider-debug {\n        &:before {\n          content: var(--intersection-margin);\n          background: rgba(236, 236, 236, 0.84);\n          color: #363636;\n          display: block;\n        }\n      }\n    }\n  }\n\n  &[itemAutoSize='true'] {\n    ::ng-deep {\n      gallery-item {\n        &:before {\n          position: absolute;\n          margin: 10px;\n          content: var(--item-intersection-margin);\n          background: rgba(236, 236, 236, 0.84);\n          color: #363636;\n          display: block;\n          width: 270px;\n          font-family: monospace;\n          z-index: 12;\n          padding: 2px 6px;\n          border-radius: 3px;\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/directives/gallery-box-def.directive.spec.ts",
    "content": "import { TestBed, ComponentFixture } from '@angular/core/testing';\nimport { Component, viewChild } from '@angular/core';\nimport { JsonPipe } from '@angular/common';\nimport { GalleryBoxDef, GalleryStateContext } from 'ng-gallery';\n\n@Component({\n  template: `\n    <div *galleryBoxDef=\"let data\">{{ data | json }}</div>\n  `,\n  imports: [GalleryBoxDef, JsonPipe]\n})\nclass TestComponent {\n  galleryBoxDef = viewChild(GalleryBoxDef);\n}\n\ndescribe('GalleryBoxDef Directive', () => {\n  let fixture: ComponentFixture<TestComponent>;\n  let component: TestComponent;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [GalleryBoxDef, TestComponent]\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(TestComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create the directive and inject the TemplateRef', () => {\n    expect(component.galleryBoxDef()).toBeTruthy();\n    expect(component.galleryBoxDef().templateRef).toBeDefined();\n  });\n\n  it('should guard the template context type', () => {\n    const context: GalleryStateContext = {};\n    expect(GalleryBoxDef.ngTemplateContextGuard(component.galleryBoxDef(), context)).toBeTrue();\n  });\n});\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/directives/gallery-box-def.directive.ts",
    "content": "import { Directive, inject, TemplateRef } from '@angular/core';\nimport { GalleryConfig } from '../models/config.model';\n\n\n@Directive({\n  selector: '[galleryBoxDef]'\n})\nexport class GalleryBoxDef {\n\n  templateRef: TemplateRef<GalleryStateContext> = inject(TemplateRef<GalleryStateContext>);\n\n  // Make sure the template checker knows the type of the context with which the\n  // template of this directive will be rendered\n  static ngTemplateContextGuard(\n    directive: GalleryBoxDef,\n    context: GalleryStateContext\n  ): context is GalleryStateContext {\n    return true;\n  }\n}\n\nexport interface GalleryStateContext {\n  config?: GalleryConfig;\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/directives/gallery-item-def.directive.spec.ts",
    "content": "import { TestBed, ComponentFixture } from '@angular/core/testing';\nimport { Component, viewChild } from '@angular/core';\nimport { JsonPipe } from '@angular/common';\nimport { GalleryItemData, GalleryItemDef } from 'ng-gallery';\nimport { GalleryItemContext } from './gallery-item-def.directive';\n\n@Component({\n  template: `\n    <div *galleryItemDef=\"let data\">{{ data | json }}</div>\n  `,\n  imports: [GalleryItemDef, JsonPipe]\n})\nclass TestComponent {\n  galleryItemDef = viewChild(GalleryItemDef);\n}\n\ndescribe('GalleryItemDef Directive', () => {\n  let fixture: ComponentFixture<TestComponent>;\n  let component: TestComponent;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [GalleryItemDef, TestComponent]\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(TestComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n  });\n\n  it('should create the directive and inject the TemplateRef', () => {\n    expect(component.galleryItemDef()).toBeTruthy();\n    expect(component.galleryItemDef().templateRef).toBeDefined();\n  });\n\n  it('should guard the template context type', () => {\n    const context: GalleryItemContext<GalleryItemData> = {} as GalleryItemContext<GalleryItemData>;\n\n    expect(GalleryItemDef.ngTemplateContextGuard(component.galleryItemDef(), context)).toBeTrue();\n  });\n});\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/directives/gallery-item-def.directive.ts",
    "content": "import { Directive, inject, TemplateRef } from '@angular/core';\nimport { GalleryItemData } from '../templates/items.model';\n\n@Directive({\n  selector: '[galleryItemDef]'\n})\nexport class GalleryItemDef {\n  templateRef: TemplateRef<GalleryItemContext<GalleryItemData>> = inject(TemplateRef<GalleryItemContext<GalleryItemData>>);\n\n  // Make sure the template checker knows the type of the context with which the\n  // template of this directive will be rendered\n  static ngTemplateContextGuard(\n    directive: GalleryItemDef,\n    context: GalleryItemContext<GalleryItemData>\n  ): context is GalleryItemContext<GalleryItemData> {\n    return true;\n  }\n}\n\nexport interface GalleryItemContext<T> {\n  /** Data for the row that this cell is located within. */\n  $implicit?: T;\n\n  /** Index of the item. */\n  index?: number;\n\n  /** True if this item is the active one. */\n  active?: boolean;\n\n  /** The number of total items. */\n  count?: number;\n\n  /** True if this item is first. */\n  first?: boolean;\n\n  /** True if this item is last. */\n  last?: boolean;\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/gallery.module.ts",
    "content": "import { NgModule } from '@angular/core';\nimport { GalleryComponent } from './core/gallery.component';\nimport { GalleryItemDef } from './directives/gallery-item-def.directive';\nimport { GalleryBoxDef } from './directives/gallery-box-def.directive';\n\n@NgModule({\n  imports: [\n    GalleryComponent,\n    GalleryItemDef,\n    GalleryBoxDef\n  ],\n  exports: [\n    GalleryComponent,\n    GalleryItemDef,\n    GalleryBoxDef\n  ]\n})\nexport class GalleryModule {\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/gestures/hammer-slider.spec.ts",
    "content": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { By } from '@angular/platform-browser';\nimport { DebugElement } from '@angular/core';\nimport { TestComponent } from '../tests/common';\nimport { HammerSliding } from './hammer-sliding.directive';\nimport 'hammerjs';\n\ndescribe('Hammer slider directive', () => {\n  let fixture: ComponentFixture<TestComponent>;\n  let hammerSliderElement: DebugElement\n  let hammerSliderDirective: HammerSliding;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [\n        NoopAnimationsModule,\n        TestComponent\n      ],\n      providers: [\n        { provide: ComponentFixtureAutoDetect, useValue: true }\n      ]\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(TestComponent);\n    fixture.detectChanges();\n\n    hammerSliderElement = fixture.debugElement.query(By.directive(HammerSliding));\n    hammerSliderDirective = hammerSliderElement.injector.get(HammerSliding);\n  });\n\n  it('should create [hammerSlider] directive', () => {\n    expect(hammerSliderDirective).toBeTruthy();\n  });\n\n  // TODO: Failed to simulate sliding events\n  // fit('should trigger panstart and set sliding to true',async () => {\n  //   // Create a custom event with properties expected by HammerJS\n  //   fixture.detectChanges();\n  //   await afterTimeout(1000);\n  //\n  //   // Trigger the event\n  //   nativeElement.dispatchEvent(new Event('pan'));\n  //\n  //   // Assert that the sliding signal is set to true\n  //   expect(hammerSliderDirective.sliding()).toBeTrue();\n  // });\n});\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/gestures/hammer-sliding.directive.ts",
    "content": "import {\n  Directive,\n  inject,\n  signal,\n  effect,\n  untracked,\n  booleanAttribute,\n  input,\n  NgZone,\n  ElementRef,\n  WritableSignal,\n  EffectCleanupRegisterFn,\n  InputSignalWithTransform\n} from '@angular/core';\nimport { DOCUMENT } from '@angular/common';\nimport { HammerGestureConfig } from '@angular/platform-browser';\nimport { Platform } from '@angular/cdk/platform';\nimport { Directionality } from '@angular/cdk/bidi';\nimport { take } from 'rxjs';\n\nimport { ORIENTATION } from '../models/constants';\nimport { GalleryRef } from '../services/gallery-ref';\nimport { GalleryConfig } from '../models/config.model';\nimport { SliderAdapter } from '../slider/adapters';\nimport { CustomHammerConfig, HammerInstance } from '../services/hammer';\nimport { createIntersectionObserver } from '../observers/intersection-observer';\nimport { SliderComponent } from '../slider/slider/slider';\n\n@Directive({\n  selector: '[hammerSliding]',\n  host: {\n    '[class.g-sliding]': 'sliding()'\n  },\n  providers: [{ provide: HammerGestureConfig, useClass: CustomHammerConfig }]\n})\nexport class HammerSliding {\n\n  private readonly hammer: HammerGestureConfig = inject(HammerGestureConfig);\n\n  private readonly galleryRef: GalleryRef = inject(GalleryRef);\n\n  private readonly _viewport: HTMLElement = inject(ElementRef<HTMLElement>).nativeElement;\n\n  private readonly _document: Document = inject(DOCUMENT);\n\n  private readonly _dir: Directionality = inject(Directionality);\n\n  private readonly _platform: Platform = inject(Platform);\n\n  private readonly _zone: NgZone = inject(NgZone);\n\n  private readonly slider: SliderComponent = inject(SliderComponent, { self: true });\n\n  sliding: WritableSignal<boolean> = signal<boolean>(false);\n\n  isThumbs: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(false, {\n    transform: booleanAttribute\n  });\n\n  constructor() {\n    if (this._platform.ANDROID || this._platform.IOS || !(this._document.defaultView as any).Hammer) return;\n\n    // HammerJS instance\n    let mc: HammerInstance;\n\n    effect((onCleanup: EffectCleanupRegisterFn) => {\n      const config: GalleryConfig = this.galleryRef.config();\n      const adapter: SliderAdapter = this.slider.adapter();\n\n      if (!adapter && !config.disableMouseScroll) return;\n\n      untracked(() => {\n        this._zone.runOutsideAngular(() => {\n          const direction: number = adapter.hammerDirection;\n\n          this.hammer.overrides.pan = { direction };\n          mc = this.hammer.buildHammer(this._viewport);\n\n          let offset: number;\n\n          // Set panOffset for sliding on pan start event\n          mc.on('panstart', () => {\n            this._zone.run(() => {\n              this.sliding.set(true);\n            });\n\n            offset = adapter.scrollValue;\n          });\n\n          mc.on('panmove', (e: any) => {\n            this._viewport.scrollTo(adapter.getHammerValue(offset, e, 'auto'));\n          });\n\n          mc.on('panend', (e: any) => {\n            this._document.onselectstart = null;\n\n            if (this.isThumbs()) {\n              this.sliding.set(false);\n              return;\n            }\n\n            const index: number = this.getIndexOnMouseUp(e, this.slider.adapter());\n            if (index !== -1) {\n              this._zone.run(() => {\n                this.galleryRef.set(index);\n                // Tiny delay is needed to avoid flicker positioning when scroll-snap is toggled\n                // requestAnimationFrame(() => {\n                  this.sliding.set(false);\n                // });\n              });\n              return;\n            }\n\n            const visibleEntries: IntersectionObserverEntry[] = Object.values(this.slider.visibleEntries());\n\n            const visibleElements: Element[] = visibleEntries.map((entry: IntersectionObserverEntry) => entry.target);\n\n            // Get the diff between the viewport size and the smallest visible item size\n            const diffSize: number = visibleEntries.reduce((total: number, entry: IntersectionObserverEntry) => {\n              return Math.max(total, (this._viewport.clientWidth - entry.boundingClientRect.width) / 2);\n            }, 0);\n\n            const options: IntersectionObserverInit = {\n              root: this._viewport,\n              threshold: 0,\n              rootMargin: `0px ${ -diffSize }px 0px ${ -diffSize }px`\n            };\n\n            createIntersectionObserver(options, visibleElements).pipe(\n              take(1)\n            ).subscribe((entries: IntersectionObserverEntry[]) => {\n\n              const centerElement: IntersectionObserverEntry = entries\n                .filter((entry: IntersectionObserverEntry) => entry.isIntersecting)\n                .reduce((acc: IntersectionObserverEntry, entry: IntersectionObserverEntry) => {\n                  return acc ? acc.intersectionRatio > entry.intersectionRatio ? acc : entry : entry;\n                }, null);\n\n              this._zone.run(() => {\n                const index: number = +centerElement.target.getAttribute('galleryIndex');\n                this.galleryRef.set(index);\n                // Tiny delay is needed to avoid flicker positioning when scroll-snap is toggled\n                // requestAnimationFrame(() => {\n                  this.sliding.set(false);\n                // });\n              });\n            })\n          });\n        });\n\n        onCleanup(() => mc?.destroy());\n      });\n    });\n  }\n\n  private getIndexOnMouseUp(e: any, adapter: SliderAdapter): number {\n    const currIndex: number = this.galleryRef.currIndex();\n\n    const velocity: number = adapter.getHammerVelocity(e);\n    // Check if velocity is great enough to navigate\n    if (Math.abs(velocity) > 0.3) {\n      if (this.galleryRef.config().orientation === ORIENTATION.Horizontal) {\n        if (velocity > 0) {\n          return this._dir.value === 'rtl' ? currIndex + 1 : currIndex - 1;\n        }\n        return this._dir.value === 'rtl' ? currIndex - 1 : currIndex + 1;\n      } else {\n        return velocity > 0 ? currIndex - 1 : currIndex + 1;\n      }\n    }\n\n    // Reset position to the current index\n    return -1;\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/gestures/mouse-sliding.directive.ts",
    "content": "// import { Directive, Inject, Input, Output, OnChanges, OnDestroy, SimpleChanges, NgZone, ElementRef, EventEmitter } from '@angular/core';\n// import { DOCUMENT } from '@angular/common';\n// import { Observable, Subscription, fromEvent, switchMap, take, takeUntil, tap } from 'rxjs';\n// import { SliderAdapter } from '../components/adapters';\n// import { GalleryConfig } from '../models/config.model';\n// import { GalleryState } from '../models/gallery.model';\n//\n// @Directive({\n//   selector: '[mouseSliding]',\n//   standalone: true\n// })\n// export class MouseSliding implements OnChanges, OnDestroy {\n//\n//   private _currentSubscription: Subscription;\n//\n//   get _viewport(): HTMLElement {\n//     return this._el.nativeElement;\n//   }\n//\n//   @Input('mouseSliding') galleryId: string;\n//\n//   @Input() items: HTMLElement[];\n//\n//   @Input() adapter: SliderAdapter;\n//\n//   @Input() state: GalleryState;\n//\n//   @Input() config: GalleryConfig;\n//\n//   @Output() activeIndexChange: EventEmitter<number> = new EventEmitter<number>();\n//\n//   constructor(@Inject(DOCUMENT) private _document: Document,\n//               private _zone: NgZone,\n//               private _el: ElementRef<HTMLElement>) {\n//   }\n//\n//\n//   ngOnChanges(changes: SimpleChanges): void {\n//     if (changes.config && changes.config.currentValue?.mouseScrollDisabled !== changes.config.previousValue?.mouseScrollDisabled) {\n//       changes.config.currentValue.mouseScrollDisabled ? this._unsubscribe() : this._subscribe();\n//     }\n//   }\n//\n//   ngOnDestroy(): void {\n//     this._unsubscribe();\n//   }\n//\n//   private _subscribe(): void {\n//     this._unsubscribe();\n//\n//     this._zone.runOutsideAngular(() => {\n//       this._currentSubscription = this.dragEvent().subscribe();\n//     });\n//   }\n//\n//   private _unsubscribe(): void {\n//     this._currentSubscription?.unsubscribe();\n//   }\n//\n//   private dragEvent(): Observable<any> {\n//     const mouseDown$: Observable<MouseEvent> = fromEvent<MouseEvent>(this._viewport, 'mousedown');\n//     const mouseUp$: Observable<MouseEvent> = fromEvent<MouseEvent>(this._document, 'mouseup');\n//     const mouseMove$: Observable<MouseEvent> = fromEvent<MouseEvent>(this._document, 'mousemove', {\n//       capture: true,\n//       passive: true\n//     });\n//\n//     let offset: number;\n//     let velocity: number;\n//\n//     const dragStart: Observable<MouseEvent> = mouseDown$.pipe(\n//       tap((downEvent: MouseEvent) => {\n//         downEvent.preventDefault();\n//         offset = this.adapter.scrollValue;\n//         this._viewport.style.scrollSnapType = 'unset';\n//         this._viewport.classList.add('g-sliding');\n//         this._document.onselectstart = () => false;\n//       })\n//     );\n//\n//     const dragEnd: Observable<MouseEvent> = mouseUp$.pipe(\n//       tap((upEvent: MouseEvent) => {\n//         this._document.onselectstart = null;\n//         this._viewport.classList.remove('g-sliding');\n//         const index: number = this.getIndexOnMouseUp(velocity);\n//         this._zone.run(() => this.activeIndexChange.emit(index));\n//       }),\n//       take(1)\n//     );\n//\n//     return dragStart.pipe(\n//       switchMap((startEvent: MouseEvent) => {\n//         return mouseMove$.pipe(\n//           tap((moveEvent: MouseEvent) => {\n//             const start: number = this.adapter.getDraggingProperty(startEvent);\n//             const current: number = this.adapter.getDraggingProperty(moveEvent);\n//             const deltaTime: number = moveEvent.timeStamp - startEvent.timeStamp;\n//             const delta: number = current - start;\n//             velocity = delta / deltaTime;\n//             this._viewport.scrollTo(this.adapter.getDraggingValue(offset, delta, 'auto'));\n//           }),\n//           takeUntil(dragEnd)\n//         );\n//       })\n//     );\n//   }\n//\n//   private getIndexOnMouseUp(velocity: number): number {\n//     // Check if scrolled item is great enough to navigate\n//     const currElement: Element = this.items[this.state.currIndex];\n//\n//     // Find the gallery item element in the center elements\n//     const elementAtCenter: Element = this.getElementFromViewportCenter();\n//\n//     // Check if center item can be taken from element using\n//     if (elementAtCenter && elementAtCenter !== currElement) {\n//       return +elementAtCenter.getAttribute('galleryIndex');\n//     }\n//\n//     // Check if velocity is great enough to navigate\n//     if (Math.abs(velocity) > 0.3) {\n//       return velocity > 0 ? this.state.currIndex - 1 : this.state.currIndex + 1;\n//     }\n//\n//     // Reset position to the current index\n//     return -1;\n//   }\n//\n//   private getElementFromViewportCenter(): Element {\n//     // Get slider position relative to the document\n//     const sliderRect: DOMRect = this._viewport.getBoundingClientRect();\n//     // Try look for the center item using `elementsFromPoint` function\n//     const centerElements: Element[] = this._document.elementsFromPoint(\n//       sliderRect.x + (sliderRect.width / 2),\n//       sliderRect.y + (sliderRect.height / 2)\n//     );\n//     // Find the gallery item element in the center elements\n//     return centerElements.find((element: Element) => {\n//       return element.getAttribute('galleryId') === this.galleryId;\n//     });\n//   }\n// }\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/models/config.model.ts",
    "content": "import { InjectionToken, Provider, TemplateRef } from '@angular/core';\nimport { BezierEasingOptions } from '../smooth-scroll';\nimport { defaultConfig } from '../utils/gallery.default';\nimport { GalleryItemData } from '../templates/items.model';\n\nexport const GALLERY_CONFIG: InjectionToken<GalleryConfig> = new InjectionToken<GalleryConfig>('GALLERY_CONFIG', {\n  providedIn: 'root',\n  factory: () => defaultConfig\n});\n\nexport function provideGalleryOptions(options: GalleryConfig): Provider {\n  return {\n    provide: GALLERY_CONFIG,\n    useValue: { ...defaultConfig, ...options }\n  }\n}\n\nexport type ImageSize = 'contain' | 'cover';\n\nexport type Orientation = 'horizontal' | 'vertical';\n\nexport type ThumbsPosition = 'top' | 'left' | 'right' | 'bottom';\n\nexport type HorizontalPosition = 'top' | 'bottom';\n\ninterface ThumbConfig {\n  thumbLoadingIcon?: string;\n  thumbLoadingError?: string;\n}\n\ninterface NavConfig {\n  navIcon?: string;\n}\n\ninterface PlayerConfig {\n  autoplay?: boolean;\n  autoplayInterval?: number;\n}\n\ninterface SliderConfig {\n  loop?: boolean;\n  disableScroll?: boolean;\n  disableMouseScroll?: boolean;\n  itemAutosize?: boolean;\n  loadingIcon?: string;\n  loadingError?: string;\n  scrollDuration?: number;\n  scrollEase?: BezierEasingOptions;\n  orientation?: Orientation;\n  imageSize?: ImageSize;\n  centralized?: boolean;\n}\n\nexport type GalleryConfig = SliderConfig\n  & ThumbConfig\n  & NavConfig\n  & PlayerConfig\n  & {\n  scrollBehavior?: ScrollBehavior;\n  resizeDebounceTime?: number;\n  debug?: boolean;\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/models/constants.ts",
    "content": "export enum IMAGE_SIZE {\n  Cover = 'cover',\n  Contain = 'contain'\n}\n\nexport enum LOADING_STRATEGY {\n  Preload = 'preload',\n  Lazy = 'lazy',\n  Default = 'default'\n}\n\nexport enum LOADING_ATTR {\n  Eager= 'eager',\n  Lazy = 'lazy'\n}\n\nexport enum THUMB_POSITION {\n  Top = 'top',\n  Left = 'left',\n  Right = 'right',\n  Bottom = 'bottom'\n}\n\nexport enum BulletsPosition {\n  Top = 'top',\n  Bottom = 'bottom'\n}\n\nexport enum CounterPosition {\n  Top = 'top',\n  Bottom = 'bottom'\n}\n\nexport enum ORIENTATION {\n  Horizontal = 'horizontal',\n  Vertical = 'vertical'\n}\n\nexport enum ITEM_TYPE {\n  Image = 'image',\n  Video = 'video',\n  Youtube = 'youtube',\n  Vimeo = 'vimeo',\n  Iframe = 'iframe'\n}\n\nexport type GalleryItemType = ITEM_TYPE | string;\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/models/item.model.ts",
    "content": "export type ItemState = 'success' | 'loading' | 'failed';\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/models/slider.model.ts",
    "content": "export interface SliderState {\n  style: any;\n  instant: boolean;\n}\n\nexport interface WorkerState {\n  value: number;\n  instant: boolean;\n}\n\nexport interface IndexChange {\n  index: number;\n  behavior: ScrollBehavior;\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/models/styles.model.ts",
    "content": ""
  },
  {
    "path": "projects/ng-gallery/src/lib/nav/gallery-nav.component.ts",
    "content": "import { Component, inject, computed, input, Signal, InputSignal, ChangeDetectionStrategy } from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { Directionality } from '@angular/cdk/bidi';\nimport { GalleryRef } from '../services/gallery-ref';\n\n@Component({\n  host: {\n    '[attr.dir]': 'dir.value'\n  },\n  selector: 'gallery-nav',\n  template: `\n    @if (galleryRef.config().loop || galleryRef.hasPrev()) {\n      <i class=\"g-nav-prev\"\n         aria-label=\"Previous\"\n         role=\"button\"\n         (click)=\"galleryRef.prev(scrollBehavior())\"\n         [innerHtml]=\"navIcon()\"></i>\n    }\n    @if (galleryRef.config().loop || galleryRef.hasNext()) {\n      <i class=\"g-nav-next\"\n         aria-label=\"Next\"\n         role=\"button\"\n         (click)=\"galleryRef.next(scrollBehavior())\"\n         [innerHtml]=\"navIcon()\"></i>\n    }\n  `,\n  styleUrl: './gallery-nav.scss',\n  changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class GalleryNavComponent {\n\n  readonly dir: Directionality = inject(Directionality);\n\n  private readonly _sanitizer: DomSanitizer = inject(DomSanitizer);\n\n  readonly galleryRef: GalleryRef = inject(GalleryRef);\n\n  readonly navIcon: Signal<SafeHtml> = computed(() =>\n    this._sanitizer.bypassSecurityTrustHtml(this.galleryRef.config().navIcon)\n  );\n\n  readonly scrollBehavior: InputSignal<ScrollBehavior> = input<ScrollBehavior>('smooth');\n\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/nav/gallery-nav.scss",
    "content": ":host {\n  // Gallery nav variables\n  --nav-space: 8px;\n  --nav-hover-space: 6.4px;\n  --nav-next-right: unset;\n  --nav-next-hover-right: unset;\n  --nav-next-left: unset;\n  --nav-next-hover-left: unset;\n\n  &[dir='ltr'] {\n    --nav-next-transform: translateY(-50%) perspective(1px);\n    --nav-next-right: var(--nav-space);\n    --nav-next-hover-right: var(--nav-hover-space);\n\n    --nav-prev-transform: translateY(-50%) perspective(1px) scale(-1, -1);\n    --nav-prev-left: var(--nav-space);\n    --nav-prev-hover-left: var(--nav-hover-space);\n  }\n\n  &[dir='rtl'] {\n    --nav-next-transform: translateY(-50%) perspective(1px) scale(-1, -1);\n    --nav-next-left: var(--nav-space);\n    --nav-next-hover-left: var(--nav-hover-space);\n\n    --nav-prev-transform: translateY(-50%) perspective(1px);\n    --nav-prev-right: var(--nav-space);\n    --nav-prev-hover-right: var(--nav-hover-space);\n  }\n}\n\n.g-nav-next,\n.g-nav-prev {\n  position: absolute;\n  top: 50%;\n  display: flex;\n  padding: 16px 8px;\n  cursor: pointer;\n  z-index: 999;\n  opacity: 0.6;\n  transition: opacity linear 150ms, right linear 150ms, left linear 150ms;\n\n  &:hover {\n    opacity: 1;\n  }\n\n  ::ng-deep {\n    svg {\n      filter: var(--g-nav-drop-shadow);\n      width: 28px;\n      height: 28px;\n      fill: #fff;\n    }\n  }\n}\n\n.g-nav-next {\n  left: var(--nav-next-left);\n  right: var(--nav-next-right);\n  transform: var(--nav-next-transform);\n\n  &:hover {\n    left: var(--nav-next-hover-left);\n    right: var(--nav-next-hover-right);\n  }\n}\n\n.g-nav-prev {\n  left: var(--nav-prev-left);\n  right: var(--nav-prev-right);\n  transform: var(--nav-prev-transform);\n\n  &:hover {\n    left: var(--nav-prev-hover-left);\n    right: var(--nav-prev-hover-right);\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/nav/gallery-nav.spec.ts",
    "content": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { Component, DebugElement, Signal, viewChild } from '@angular/core';\nimport { By } from '@angular/platform-browser';\nimport {\n  GalleryNavComponent,\n  GalleryComponent,\n  GalleryItemData,\n  GalleryItemDef,\n  GalleryRef,\n  ImgRecognizer\n} from 'ng-gallery';\nimport { img1, img2, img3 } from '../tests/test-images';\nimport { Dir, Direction } from '@angular/cdk/bidi';\n\n@Component({\n  imports: [GalleryComponent, Dir, GalleryNavComponent, GalleryItemDef, ImgRecognizer],\n  template: `\n    <gallery [dir]=\"dir\" [items]=\"items\" [style.width.px]=\"width\" [style.height.px]=\"height\">\n      <img *galleryItemDef=\"let item\"\n           galleryImage\n           [src]=\"item.src\"/>\n\n      <gallery-nav [scrollBehavior]=\"scrollBehavior\"/>\n    </gallery>\n  `\n})\nexport class TestComponent {\n  items: GalleryItemData[] = [\n    { src: img1 },\n    { src: img2 },\n    { src: img3 }\n  ];\n  width: number = 500;\n  height: number = 300;\n\n  scrollBehavior: ScrollBehavior = 'smooth';\n  dir: Direction = 'ltr';\n\n  gallery: Signal<GalleryComponent> = viewChild(GalleryComponent);\n}\n\ndescribe('Gallery nav component', () => {\n  let fixture: ComponentFixture<TestComponent>;\n  let component: TestComponent;\n  let navComponent: GalleryNavComponent;\n  let galleryRef: GalleryRef;\n  let navComponentElement: DebugElement;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [\n        NoopAnimationsModule,\n        TestComponent\n      ],\n      providers: [\n        { provide: ComponentFixtureAutoDetect, useValue: true }\n      ]\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(TestComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n\n    navComponentElement = fixture.debugElement.query(By.directive(GalleryNavComponent));\n    navComponent = navComponentElement.injector.get(GalleryNavComponent);\n    galleryRef = navComponentElement.injector.get(GalleryRef);\n  });\n\n  it('should create gallery-nav component', () => {\n    expect(navComponent).toBeTruthy();\n    expect(galleryRef).toBeTruthy();\n    expect(navComponent.dir).toBeTruthy();\n    expect(navComponent.navIcon()).toBeTruthy();\n  });\n\n  it('should set dir attribute', () => {\n    expect((navComponentElement.nativeElement as HTMLElement).getAttribute('dir')).toBe('ltr');\n\n    component.dir = 'rtl';\n    fixture.detectChanges();\n\n    expect((navComponentElement.nativeElement as HTMLElement).getAttribute('dir')).toBe('rtl');\n  });\n\n  it('should set the scrollBehavior input', () => {\n    expect(navComponent.scrollBehavior()).toBe('smooth');\n\n    // Change size value\n    component.scrollBehavior = 'auto';\n    fixture.detectChanges();\n\n    expect(navComponent.scrollBehavior()).toBe('auto');\n  });\n});\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/observers/intersection-directive.spec.ts",
    "content": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { By } from '@angular/platform-browser';\nimport { DebugElement } from '@angular/core';\nimport { GalleryRef } from 'ng-gallery';\nimport { getObservableFromContext, TestComponent } from '../tests/common';\nimport { IntersectionSensor } from './intersection-sensor.directive';\nimport { filter, firstValueFrom, Observable } from 'rxjs';\n\ndescribe('Intersection directive', () => {\n  let fixture: ComponentFixture<TestComponent>;\n  let intersectionSensorDirective: IntersectionSensor;\n  let galleryRef: GalleryRef;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [\n        NoopAnimationsModule,\n        TestComponent\n      ],\n      providers: [\n        { provide: ComponentFixtureAutoDetect, useValue: true }\n      ]\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(TestComponent);\n    fixture.detectChanges();\n\n    const intersectionSensorElement: DebugElement = fixture.debugElement.query(By.directive(IntersectionSensor));\n    intersectionSensorDirective = intersectionSensorElement.injector.get(IntersectionSensor);\n    galleryRef = intersectionSensorElement.injector.get(GalleryRef);\n  });\n\n  it('should create [intersectionSensor] directive', () => {\n    expect(intersectionSensorDirective).toBeTruthy();\n  });\n\n  it('should observe when items become visible as soon as possible', async () => {\n    await firstValueFrom(galleryRef.afterItemsVisible);\n\n    const visibleItems: Record<number, IntersectionObserverEntry> = galleryRef.visibleItems();\n    const element: Element = visibleItems[0].target;\n    const queryElement: DebugElement = fixture.debugElement.query(By.css('slider-item.g-item-highlight'));\n\n    expect(Object.keys(visibleItems).length).toBe(1);\n    expect(element).toBe(queryElement.nativeElement);\n    expect(element.classList.contains('g-item-highlight')).toBe(true);\n    expect(galleryRef.currIndex()).toBe(0);\n  });\n\n  it('should detect when next item becomes visible on scroll then detect the previous leave after scroll', async () => {\n    await firstValueFrom(galleryRef.afterItemsVisible);\n\n    expect(galleryRef.currIndex()).toBe(0);\n    galleryRef.next();\n\n    // Wait for scroll starts and the next item is detected, at this point both previous and next items are visible\n\n    const visibleItemIsTwo$: Observable<any> = getObservableFromContext(galleryRef.visibleItems).pipe(\n      filter((obj: Record<number, IntersectionObserverEntry>) => Object.keys(obj).length === 2)\n    );\n    await firstValueFrom(visibleItemIsTwo$);\n\n    const visibleItems: Record<number, IntersectionObserverEntry> = galleryRef.visibleItems();\n    const queryElements: DebugElement[] = fixture.debugElement.queryAll(By.css('slider-item.g-item-highlight'));\n\n    expect(Object.keys(visibleItems).length).toBe(2);\n    expect(visibleItems[0].target).toBe(queryElements[0].nativeElement);\n    expect(visibleItems[1].target).toBe(queryElements[1].nativeElement);\n\n    // Wait until scroll is ended and the new active item is set\n\n    const arrivedToNextItem$: Observable<any> = galleryRef.indexChanged.pipe(\n      filter((currIndex: number) => currIndex === 1)\n    );\n    await firstValueFrom(arrivedToNextItem$);\n\n    const visibleItemsAfter: Record<number, IntersectionObserverEntry> = galleryRef.visibleItems();\n    const queryElementsAfter: DebugElement[] = fixture.debugElement.queryAll(By.css('slider-item.g-item-highlight'));\n\n    expect(Object.keys(visibleItems).length).toBe(1);\n    expect(visibleItemsAfter[1].target).toBe(queryElementsAfter[0].nativeElement);\n    expect(galleryRef.currIndex()).toBe(1);\n  });\n});\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/observers/intersection-observer.ts",
    "content": "import { Observable, Subscriber } from 'rxjs';\n\nexport function createIntersectionObserver(options: IntersectionObserverInit, elements: Element[]): Observable<IntersectionObserverEntry[]> {\n  return new Observable((observer: Subscriber<IntersectionObserverEntry[]>) => {\n    const intersectionObserver: IntersectionObserver = new IntersectionObserver(\n      (entries: IntersectionObserverEntry[]) => observer.next(entries),\n      options\n    );\n    elements.forEach((element: HTMLElement) => intersectionObserver.observe(element));\n    return () => {\n      elements.forEach((element: HTMLElement) => intersectionObserver.unobserve(element));\n      intersectionObserver.disconnect();\n    };\n  });\n}\n\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/observers/intersection-sensor.directive.ts",
    "content": "import {\n  Directive,\n  inject,\n  effect,\n  computed,\n  untracked,\n  Signal,\n  NgZone,\n  ElementRef,\n  EffectCleanupRegisterFn\n} from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport { GalleryConfig } from '../models/config.model';\nimport { GalleryRef } from '../services/gallery-ref';\nimport { SliderAdapter } from '../slider/adapters';\nimport { SliderItem } from '../slider/slider-item/slider-item';\nimport { createIntersectionObserver } from './intersection-observer';\nimport { SmoothScroll } from '../smooth-scroll';\nimport { HammerSliding } from '../gestures/hammer-sliding.directive';\nimport { SliderComponent } from '../slider/slider/slider';\n\n/**\n * This observer used to detect when a slider element reaches the active soon\n */\n@Directive({\n  selector: '[intersectionSensor]'\n})\nexport class IntersectionSensor {\n\n  private readonly zone: NgZone = inject(NgZone);\n\n  private readonly galleryRef: GalleryRef = inject(GalleryRef);\n\n  private readonly smoothScroll: SmoothScroll = inject(SmoothScroll);\n\n  private readonly hammerSlider: HammerSliding = inject(HammerSliding);\n\n  private readonly nativeElement: HTMLElement = inject(ElementRef<HTMLElement>).nativeElement;\n\n  private readonly slider: SliderComponent = inject(SliderComponent, { self: true });\n\n  readonly disableInteractionObserver: Signal<boolean> = computed(() => {\n    return this.smoothScroll.scrolling() || this.hammerSlider.sliding(); // || this.resizeSensor.isResizing();\n  });\n\n  constructor() {\n    let visibleItemsObserver$: Subscription;\n    let activeItemObserver$: Subscription;\n\n    effect((onCleanup: EffectCleanupRegisterFn) => {\n      const config: GalleryConfig = this.galleryRef.config();\n      const items: ReadonlyArray<SliderItem> = this.slider.items();\n      const adapter: SliderAdapter = this.slider.adapter();\n\n      if (!adapter || !items.length) return;\n\n      untracked(() => {\n        const rootMargin: string = adapter.getRootMargin();\n        if (config.debug) {\n          this.nativeElement.style.setProperty('--intersection-margin', `\"INTERSECTION(${ rootMargin })\"`);\n        }\n\n        this.zone.runOutsideAngular(() => {\n          const options: IntersectionObserverInit = { root: this.nativeElement, threshold: 0.1, rootMargin };\n          const elements: HTMLElement[] = items.map((item: SliderItem) => item.nativeElement);\n\n          visibleItemsObserver$ = createIntersectionObserver(options, elements).subscribe((entries: IntersectionObserverEntry[]) => {\n            const visibleItems: Record<number, IntersectionObserverEntry> = this.slider.visibleEntries();\n            entries.forEach((entry: IntersectionObserverEntry) => {\n              if (entry.isIntersecting) {\n                entry.target.classList.add('g-item-highlight');\n                visibleItems[+entry.target.getAttribute('galleryIndex')] = entry;\n              } else {\n                entry.target.classList.remove('g-item-highlight');\n                delete visibleItems[+entry.target.getAttribute('galleryIndex')];\n              }\n            });\n            this.zone.run(() => {\n              this.galleryRef.afterItemsVisible.next();\n              this.slider.visibleEntries.set({ ...visibleItems });\n            });\n          });\n        });\n\n        onCleanup(() => visibleItemsObserver$?.unsubscribe());\n      });\n    });\n\n    effect((onCleanup) => {\n      const disabled: boolean = this.disableInteractionObserver();\n      const visibleElements: IntersectionObserverEntry[] = Object.values(this.slider.visibleEntries());\n\n      if (disabled) return;\n\n      // TODO: Should handle vertical orientation\n      untracked(() => {\n        const elements: Element[] = visibleElements.map((entry: IntersectionObserverEntry) => entry.target);\n\n        // Get the diff between the viewport size and the smallest visible item size\n        const diffSize: number = visibleElements.reduce((total: number, entry: IntersectionObserverEntry) => {\n          return Math.min(total, (this.nativeElement.clientWidth - entry.boundingClientRect.width) / 2);\n        }, 0);\n\n        const options: IntersectionObserverInit = {\n          root: this.nativeElement,\n          threshold: .999,\n          rootMargin: `1000px ${ -diffSize }px 1000px ${ -diffSize }px`\n        };\n\n        this.zone.runOutsideAngular(() => {\n          activeItemObserver$ = createIntersectionObserver(options, elements).subscribe((entries: IntersectionObserverEntry[]) => {\n\n            const elementWithHighestIntersectionRatio: IntersectionObserverEntry = entries\n              .filter((entry: IntersectionObserverEntry) => entry.isIntersecting)\n              .reduce((acc: IntersectionObserverEntry, entry: IntersectionObserverEntry) => {\n                return acc ? acc.intersectionRatio > entry.intersectionRatio ? acc : entry : entry;\n              }, null);\n\n            if (!elementWithHighestIntersectionRatio) return;\n\n            const index: number = +elementWithHighestIntersectionRatio.target.getAttribute('galleryIndex');\n\n            // TODO: There is a bug where index becomes 1 then goes back to 0\n            if (index === this.galleryRef.currIndex()) return;\n\n            // Set the new current index\n            this.zone.run(() => this.galleryRef.currIndex.set(index));\n          });\n        });\n\n        onCleanup(() => activeItemObserver$?.unsubscribe());\n      });\n    });\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/services/gallery-ref.ts",
    "content": "import { Injectable, computed, inject, signal, Signal, WritableSignal } from '@angular/core';\nimport { toObservable } from '@angular/core/rxjs-interop';\nimport { Observable, Subject } from 'rxjs';\nimport { GALLERY_CONFIG, GalleryConfig } from '../models/config.model';\nimport { GalleryItemData } from '../templates/items.model';\nimport { IndexChange } from '../models/slider.model';\n\n@Injectable()\nexport class GalleryRef {\n\n  readonly afterItemsVisible: Subject<void> = new Subject();\n\n  /** Stream that emits on item click */\n  readonly itemClick: Subject<number> = new Subject<number>();\n\n  /** Stream that emits on thumbnail click */\n  readonly thumbClick: Subject<number> = new Subject<number>();\n\n  /** Stream that emits when items is changed (items loaded, item added, item removed) */\n  readonly itemsChanged: Subject<void> = new Subject<void>();\n\n  /** Gallery Events */\n\n  readonly visibleItems: WritableSignal<Record<number, IntersectionObserverEntry>> = signal({});\n\n  readonly visibleThumbs: WritableSignal<Record<number, IntersectionObserverEntry>> = signal({});\n\n  readonly items: WritableSignal<GalleryItemData[]> = signal([]);\n\n  readonly currIndex: WritableSignal<number> = signal(0);\n\n  readonly isPlaying: WritableSignal<boolean> = signal(false);\n\n  readonly scrollBehavior: WritableSignal<ScrollBehavior> = signal(null);\n\n  readonly hasNext: Signal<boolean> = computed(() => this.currIndex() < this.items().length - 1);\n\n  readonly hasPrev: Signal<boolean> = computed(() => this.currIndex() > 0);\n\n  readonly indexChange: Subject<IndexChange> = new Subject<IndexChange>();\n\n  /** Stream that emits when current index is changed */\n  readonly indexChanged: Observable<number> = toObservable(this.currIndex);\n\n  /** Config signal */\n  readonly config: WritableSignal<GalleryConfig> = signal(inject(GALLERY_CONFIG));\n\n  /** Stream that emits when the player should start or stop */\n  readonly playingChanged: Observable<boolean> = toObservable(this.isPlaying);\n\n  setConfig(newConfig: GalleryConfig): void {\n    this.config.update((config: GalleryConfig) => {\n      return { ...config, ...newConfig };\n    });\n  }\n\n  /**\n   * Add gallery item\n   */\n  add(newItem: GalleryItemData, active?: boolean): void {\n    this.items.update((items: GalleryItemData[]) => {\n      return [...items, newItem];\n    });\n    if (active) {\n      this.currIndex.set(this.items().length - 1);\n    }\n    this.itemsChanged.next();\n  }\n\n  /**\n   * Remove gallery item\n   */\n  remove(i: number): void {\n    this.items.update((items: GalleryItemData[]) => {\n      return [\n        ...items.slice(0, i),\n        ...items.slice(i + 1, items.length)\n      ];\n    });\n    this.currIndex.update((currIndex: number): number => i < 1 ? currIndex : i - 1);\n    this.itemsChanged.next();\n  }\n\n  /**\n   * Load items and reset the state\n   */\n  load(items: GalleryItemData[]): void {\n    if (items) {\n      this.items.set(items);\n      this.itemsChanged.next();\n    }\n  }\n\n  /**\n   * Set active item\n   */\n  set(i: number, behavior?: ScrollBehavior): void {\n    if (i < 0 || i >= this.items().length) {\n      console.error(`[NgGallery]: Unable to set the active item because the given index (${ i }) is outside the items range!`);\n      return;\n    }\n    // this.currIndex.set(i);\n    if (behavior) {\n      this.scrollBehavior.set(behavior);\n    }\n    this.indexChange.next({ index: i, behavior });\n  }\n\n  /**\n   * Next item\n   */\n  next(behavior?: ScrollBehavior, loop: boolean = true): void {\n    if (this.hasNext()) {\n      this.set(this.currIndex() + 1, behavior);\n    } else if (loop && this.config().loop) {\n      this.set(0, behavior);\n    }\n  }\n\n  /**\n   * Prev item\n   */\n  prev(behavior?: ScrollBehavior, loop: boolean = true): void {\n    if (this.hasPrev()) {\n      this.set(this.currIndex() - 1, behavior);\n    } else if (loop && this.config().loop) {\n      this.set(this.items().length - 1, behavior);\n    }\n  }\n\n  /**\n   * Start gallery player\n   */\n  play(interval?: number): void {\n    if (interval) {\n      this.config.update((config: GalleryConfig) => {\n        return { ...config, autoplayInterval: interval };\n      });\n    }\n    this.isPlaying.set(true);\n  }\n\n  /**\n   * Stop gallery player\n   */\n  stop(): void {\n    this.isPlaying.set(false);\n  }\n\n  /**\n   * Reset gallery to initial state\n   */\n  reset(): void {\n    this.items.set([]);\n  }\n\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/services/hammer.ts",
    "content": "import { Injectable } from '@angular/core';\nimport { HammerGestureConfig } from '@angular/platform-browser';\n\nexport interface HammerInstance {\n  on(eventName: string, callback?: Function): void;\n  off(eventName: string, callback?: Function): void;\n  destroy?(): void;\n}\n\ndeclare const Hammer: any;\n\n@Injectable()\nexport class CustomHammerConfig extends HammerGestureConfig {\n  override overrides = {\n    pinch: { enable: false },\n    rotate: { enable: false }\n  };\n\n  override options = { inputClass: typeof Hammer !== 'undefined' ? Hammer.MouseInput : null };\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/services/resize-directive.spec.ts",
    "content": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { By } from '@angular/platform-browser';\nimport { DebugElement } from '@angular/core';\nimport { GalleryRef } from 'ng-gallery';\nimport { firstValueFrom } from 'rxjs';\nimport { afterTimeout, TestComponent } from '../tests/common';\nimport { SliderComponent } from '../slider/slider/slider';\nimport { ResizeSensor } from './resize-sensor';\n\ndescribe('Resize sensor directive', () => {\n  let fixture: ComponentFixture<TestComponent>;\n  let component: TestComponent;\n  let resizeSensorDirective: ResizeSensor;\n  let sliderComponent: SliderComponent;\n  let galleryRef: GalleryRef;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [\n        NoopAnimationsModule,\n        TestComponent\n      ],\n      providers: [\n        { provide: ComponentFixtureAutoDetect, useValue: true }\n      ]\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(TestComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n\n    const resizeSensorElement: DebugElement = fixture.debugElement.query(By.directive(ResizeSensor));\n    resizeSensorDirective = resizeSensorElement.injector.get(ResizeSensor);\n    galleryRef = resizeSensorElement.injector.get(GalleryRef);\n\n    const sliderComponentElement: DebugElement = fixture.debugElement.query(By.directive(SliderComponent));\n    sliderComponent = sliderComponentElement.componentInstance;\n  });\n\n  it('should create [resizeSensor] directive', () => {\n    expect(resizeSensorDirective).toBeTruthy();\n  });\n\n  it('should compute \"centralizeStart\" size when content >= viewport', async () => {\n    await firstValueFrom(galleryRef.afterItemsVisible);\n    expect(resizeSensorDirective.centralizeStart()).toBe(0);\n    expect(resizeSensorDirective.centralizeStart()).toBe(0);\n    expect(sliderComponent.nativeElement.style.getPropertyValue('--centralize-start-size')).toBe('0px');\n    expect(sliderComponent.nativeElement.style.getPropertyValue('--centralize-end-size')).toBe('0px');\n  });\n\n  it('should compute \"centralizeStart\" size when content < viewport', async () => {\n    await firstValueFrom(galleryRef.afterItemsVisible);\n    sliderComponent.galleryRef.setConfig({\n      itemAutosize: true,\n      centralized: true\n    });\n    component.width = 800;\n    component.height = 200;\n\n    fixture.detectChanges();\n\n    expect(resizeSensorDirective.centralizeStart()).toBe(100);\n    expect(resizeSensorDirective.centralizeStart()).toBe(100);\n    expect(sliderComponent.nativeElement.style.getPropertyValue('--centralize-start-size')).toBe('100px');\n    expect(sliderComponent.nativeElement.style.getPropertyValue('--centralize-end-size')).toBe('100px');\n  });\n\n  it('should compute \"centralizeStart\" size when content >= viewport', async () => {\n    await firstValueFrom(galleryRef.afterItemsVisible);\n    expect(resizeSensorDirective.centralizeStart()).toBe(0);\n    expect(resizeSensorDirective.centralizeStart()).toBe(0);\n    expect(sliderComponent.nativeElement.style.getPropertyValue('--centralize-start-size')).toBe('0px');\n    expect(sliderComponent.nativeElement.style.getPropertyValue('--centralize-end-size')).toBe('0px');\n  });\n\n  it('should update the size signal when component size changes', async () => {\n    await firstValueFrom(galleryRef.afterItemsVisible);\n    expect(resizeSensorDirective.slideSize().width).toBe(500);\n    expect(resizeSensorDirective.slideSize().height).toBe(300);\n    expect(sliderComponent.nativeElement.style.getPropertyValue('--slider-width')).toBe('500px');\n    expect(sliderComponent.nativeElement.style.getPropertyValue('--slider-height')).toBe('300px');\n\n    component.width = 400;\n    fixture.detectChanges();\n    await afterTimeout(40);\n\n    expect(resizeSensorDirective.slideSize().width).toBe(400);\n    expect(sliderComponent.nativeElement.style.getPropertyValue('--slider-width')).toBe('400px');\n  });\n\n});\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/services/resize-sensor.ts",
    "content": "import {\n  Directive,\n  signal,\n  inject,\n  computed,\n  untracked,\n  afterRenderEffect,\n  NgZone,\n  Signal,\n  ElementRef,\n  WritableSignal,\n  EffectCleanupRegisterFn\n} from '@angular/core';\nimport { SharedResizeObserver } from '@angular/cdk/observers/private';\nimport { Subscription, animationFrameScheduler, throttleTime, combineLatest } from 'rxjs';\nimport { GalleryConfig } from '../models/config.model';\nimport { GalleryRef } from './gallery-ref';\nimport { SliderComponent } from '../slider/slider/slider';\n\n@Directive({\n  selector: '[resizeSensor]',\n  host: {\n    '[style.--slider-width.px]': 'slideSize()?.width',\n    '[style.--slider-height.px]': 'slideSize()?.height',\n    '[style.--centralize-start-size.px]': 'centralizeStart()',\n    '[style.--centralize-end-size.px]': 'centralizeEnd()'\n  }\n})\nexport class ResizeSensor {\n\n  nativeElement:HTMLElement = inject(ElementRef).nativeElement;\n\n  private readonly sharedResizeObserver: SharedResizeObserver = inject(SharedResizeObserver);\n\n  private readonly zone: NgZone = inject(NgZone);\n\n  private readonly galleryRef: GalleryRef = inject(GalleryRef);\n\n  private readonly slider: SliderComponent = inject(SliderComponent, { self: true });\n\n  readonly slideSize: WritableSignal<DOMRectReadOnly> = signal(null);\n\n  readonly contentSize: WritableSignal<DOMRectReadOnly> = signal(null);\n\n  readonly undefinedSizes: Signal<boolean> = computed(() => !this.slideSize() || !this.contentSize());\n\n  readonly centralizeStart: Signal<number> = computed(() => {\n    if (this.undefinedSizes()) return 0;\n    return this.slider.adapter()?.getCentralizerStartSize();\n  });\n\n  readonly centralizeEnd: Signal<number> = computed(() => {\n    if (this.undefinedSizes()) return 0;\n    return this.slider.adapter()?.getCentralizerEndSize();\n  });\n\n  disabled: WritableSignal<boolean> = signal(false);\n\n  constructor() {\n    let resizeSubscription$: Subscription;\n\n    afterRenderEffect({\n      earlyRead: (onCleanup: EffectCleanupRegisterFn) => {\n        const config: GalleryConfig = this.galleryRef.config();\n\n        // Make sure items are rendered\n        if (!this.slider.items().length || this.disabled()) return;\n\n        untracked(() => {\n          this.zone.runOutsideAngular(() => {\n            resizeSubscription$ = combineLatest([\n              this.sharedResizeObserver.observe(this.slider.nativeElement),\n              this.sharedResizeObserver.observe(this.slider.nativeElement.firstElementChild)\n            ]).pipe(\n              throttleTime(config.resizeDebounceTime, animationFrameScheduler, {\n                leading: true,\n                trailing: true\n              }),\n            ).subscribe(([sliderEntries, contentEntries]: [ResizeObserverEntry[], ResizeObserverEntry[]]) => {\n              this.zone.run(() => {\n                if (!sliderEntries || !contentEntries) return;\n\n                if (sliderEntries[0].contentRect.height) {\n                  this.slideSize.set(sliderEntries[0].contentRect);\n                }\n\n                if (contentEntries[0].contentRect.height) {\n                  this.contentSize.set(contentEntries[0].contentRect);\n                }\n              });\n            });\n          });\n\n          onCleanup(() => resizeSubscription$?.unsubscribe());\n        });\n      }\n    });\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/services/scroll-snap-type.spec.ts",
    "content": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { By } from '@angular/platform-browser';\nimport { DebugElement } from '@angular/core';\nimport { TestComponent } from '../tests/common';\nimport { ScrollSnapType } from './scroll-snap-type';\nimport { SmoothScroll } from '../smooth-scroll';\nimport { HammerSliding } from '../gestures/hammer-sliding.directive';\nimport { SliderComponent } from '../slider/slider/slider';\n\ndescribe('Scroll snap type directive', () => {\n  let fixture: ComponentFixture<TestComponent>;\n  let scrollSnapTypeDirective: ScrollSnapType;\n  let smoothScrollDirective: SmoothScroll;\n  let hammerSliderDirective: HammerSliding;\n  let sliderComponent: SliderComponent;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [\n        NoopAnimationsModule,\n        TestComponent\n      ],\n      providers: [\n        { provide: ComponentFixtureAutoDetect, useValue: true }\n      ]\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(TestComponent);\n    fixture.detectChanges();\n\n    const smoothScrollElement: DebugElement = fixture.debugElement.query(By.directive(SmoothScroll));\n    smoothScrollDirective = smoothScrollElement.injector.get(SmoothScroll);\n\n    const scrollSnapTypeElement: DebugElement = fixture.debugElement.query(By.directive(ScrollSnapType));\n    scrollSnapTypeDirective = scrollSnapTypeElement.injector.get(ScrollSnapType);\n\n    const hammerSliderElement: DebugElement = fixture.debugElement.query(By.directive(HammerSliding));\n    hammerSliderDirective = hammerSliderElement.injector.get(HammerSliding);\n\n    const sliderComponentElement: DebugElement = fixture.debugElement.query(By.directive(SliderComponent));\n    sliderComponent = sliderComponentElement.componentInstance;\n  });\n\n  it('should create [scrollSnapType] directive', () => {\n    expect(scrollSnapTypeDirective).toBeTruthy();\n  });\n\n  it('should compute \"scrollSnapType\" to none when gallery is scrolling', () => {\n    smoothScrollDirective.scrolling.set(true);\n    fixture.detectChanges();\n\n    expect(scrollSnapTypeDirective.scrollSnapType()).toBe('none');\n    expect(sliderComponent.nativeElement.style.getPropertyValue('--slider-scroll-snap-type')).toBe('none');\n  });\n\n  it('should compute \"scrollSnapType\" to none when gallery is sliding', () => {\n    hammerSliderDirective.sliding.set(true);\n    fixture.detectChanges();\n\n    expect(scrollSnapTypeDirective.scrollSnapType()).toBe('none');\n    expect(sliderComponent.nativeElement.style.getPropertyValue('--slider-scroll-snap-type')).toBe('none');\n  });\n\n  it('should compute \"scrollSnapType\" to adapter scroll snap type value', () => {\n    smoothScrollDirective.scrolling.set(false);\n    hammerSliderDirective.sliding.set(false);\n    fixture.detectChanges();\n\n    expect(scrollSnapTypeDirective.scrollSnapType()).toBe(sliderComponent.adapter().scrollSnapType);\n    expect(sliderComponent.nativeElement.style.getPropertyValue('--slider-scroll-snap-type')).toBe(sliderComponent.adapter().scrollSnapType);\n  });\n\n});\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/services/scroll-snap-type.ts",
    "content": "import { computed, Directive, inject, Signal } from '@angular/core';\nimport { HammerSliding } from '../gestures/hammer-sliding.directive';\nimport { SmoothScroll } from '../smooth-scroll';\nimport { SliderComponent } from '../slider/slider/slider';\n\n@Directive({\n  selector: '[scrollSnapType]',\n  host: {\n    '[style.--slider-scroll-snap-type]': 'scrollSnapType()'\n  }\n})\nexport class ScrollSnapType {\n\n  private readonly smoothScroll: SmoothScroll = inject(SmoothScroll, { self: true });\n\n  private readonly hammerSliding: HammerSliding = inject(HammerSliding, { self: true });\n\n  private readonly slider: SliderComponent = inject(SliderComponent, { self: true });\n\n  scrollSnapType: Signal<string> = computed(() => {\n    if (this.smoothScroll.scrolling() || this.hammerSliding.sliding()) {\n      return 'none';\n    }\n    return this.slider.adapter().scrollSnapType;\n  });\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/adapters/base-adapter.ts",
    "content": "export abstract class SliderAdapter {\n\n  readonly abstract hammerDirection: number;\n\n  readonly abstract scrollSnapType: string;\n\n  abstract get scrollValue(): number;\n\n  abstract get clientSize(): number;\n\n  abstract get isContentLessThanContainer(): boolean;\n\n  abstract getScrollToValue(target: Element, behavior: ScrollBehavior): ScrollToOptions;\n\n  abstract getCentralizerStartSize(): number;\n\n  abstract getCentralizerEndSize(): number;\n\n  abstract getRootMargin(): string;\n\n  abstract getElementRootMargin(viewport: HTMLElement, el: HTMLElement): string;\n\n  abstract getHammerVelocity(e): number;\n\n  abstract getHammerValue(value: number, delta: number, behavior: ScrollBehavior): ScrollToOptions;\n\n  // abstract getDraggingValue(value: number, delta: number, behavior: ScrollBehavior): ScrollToOptions;\n\n  // abstract getDraggingProperty(e: MouseEvent): number;\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/adapters/index.ts",
    "content": "export * from './main-adapters';\nexport * from './base-adapter';\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/adapters/main-adapter.spec.ts",
    "content": "import { Component, ElementRef, Signal, viewChild } from '@angular/core';\nimport { ComponentFixture, TestBed } from '@angular/core/testing';\nimport {\n  DIRECTION_DOWN,\n  DIRECTION_LEFT,\n  DIRECTION_RIGHT,\n  DIRECTION_UP,\n  HorizontalAdapter,\n  VerticalAdapter\n} from './main-adapters';\nimport { GalleryConfig } from 'ng-gallery';\n\n@Component({\n  selector: 'test-component',\n  template: `\n    <div class=\"slider\" #slider>\n      <div class=\"content\" #content>\n        <div class=\"item\"></div>\n        <div class=\"item\"></div>\n        <div class=\"item\"></div>\n        <div class=\"item\"></div>\n        <div class=\"item\"></div>\n      </div>\n    </div>\n  `,\n  styles: [`\n    .item {\n      height: 50px;\n      width: 50px;\n    }\n  `]\n})\nclass TestComponent {\n  slider: Signal<ElementRef<HTMLElement>> = viewChild('slider')\n  content: Signal<ElementRef<HTMLElement>> = viewChild('content')\n}\n\ndescribe('HorizontalAdapter', () => {\n  let adapter: VerticalAdapter;\n  let fixture: ComponentFixture<TestComponent>;\n  let component: TestComponent;\n  let sliderElement: HTMLElement;\n  let contentElement: HTMLElement;\n  let config: GalleryConfig;\n\n  beforeEach(async () => {\n    await TestBed.configureTestingModule({\n      imports: [TestComponent],\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(TestComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges(); // Trigger change detection to render the template\n\n    sliderElement = component.slider().nativeElement;\n    contentElement = component.content().nativeElement;\n    config = {};\n    adapter = new VerticalAdapter(sliderElement, config);\n\n    sliderElement.style.width = '500px';\n    sliderElement.style.overflowX = 'auto';\n\n    contentElement.style.width = '1000px';\n\n    config = {}; // Or a more specific config if needed\n    adapter = new HorizontalAdapter(sliderElement, config);\n  });\n\n  it('should create an instance', () => {\n    expect(adapter).toBeTruthy();\n  });\n\n  it('should have the correct hammerDirection', () => {\n    expect(adapter.hammerDirection).toBe(DIRECTION_LEFT | DIRECTION_RIGHT);\n  });\n\n  it('should have the correct scrollSnapType', () => {\n    expect(adapter.scrollSnapType).toBe('x mandatory');\n  });\n\n  it('should return the correct scrollValue', () => {\n    sliderElement.scrollLeft = 100;\n    expect(adapter.scrollValue).toBe(100);\n  });\n\n  it('should return the correct clientSize', () => {\n    expect(adapter.clientSize).toBe(500);\n  });\n\n  it('should return the correct isContentLessThanContainer value', () => {\n    expect(adapter.isContentLessThanContainer).toBe(false); // Content is wider than container in beforeEach setup\n\n    (sliderElement.firstElementChild as HTMLElement).style.width = '400px';\n    expect(adapter.isContentLessThanContainer).toBe(true); // Content is now less than container\n  });\n\n  it('getScrollToValue should return the correct scroll options', () => {\n    const target = document.createElement('div');\n    target.style.width = '100px';\n    sliderElement.appendChild(target);\n\n    const scrollToOptions = adapter.getScrollToValue(target, 'smooth');\n    const expectedPosition = target.offsetLeft - ((adapter.clientSize - target.clientWidth) / 2);\n    expect(scrollToOptions).toEqual({\n      behavior: 'smooth',\n      start: expectedPosition\n    });\n  });\n\n  it('getRootMargin should return the correct root margin', () => {\n    expect(adapter.getRootMargin()).toBe('1000px 0px 1000px 0px');\n  });\n\n  it('getElementRootMargin should return the correct element root margin', () => {\n    const viewport = document.createElement('div');\n    viewport.style.width = '500px';\n    const el = document.createElement('div');\n    el.style.width = '100px';\n\n    const rootMargin = -1 * ((viewport.clientWidth - el.clientWidth) / 2) + 1;\n    expect(adapter.getElementRootMargin(viewport, el)).toBe(`0px ${ rootMargin }px 0px ${ rootMargin }px`);\n  });\n\n  it('getCentralizerStartSize should return the correct size when content is less than container', () => {\n    (sliderElement.firstElementChild as HTMLElement).style.width = '400px';\n    const size = adapter.clientSize - (sliderElement.firstElementChild as HTMLElement).clientWidth;\n    expect(adapter.getCentralizerStartSize()).toBe(size / 2);\n  });\n\n  it('getCentralizerStartSize should return the correct size when content is greater than or equal to container', () => {\n    const expectedSize = (adapter.clientSize / 2) - ((sliderElement.firstElementChild as HTMLElement).firstElementChild?.clientWidth / 2);\n    expect(adapter.getCentralizerStartSize()).toBe(expectedSize);\n  });\n\n  it('getCentralizerEndSize should return the correct size when content is less than container', () => {\n    (sliderElement.firstElementChild as HTMLElement).style.width = '400px';\n    const size = adapter.clientSize - (sliderElement.firstElementChild as HTMLElement).clientWidth;\n    expect(adapter.getCentralizerEndSize()).toBe(size / 2);\n  });\n\n  it('getCentralizerEndSize should return the correct size when content is greater than or equal to container', () => {\n    const expectedSize = (adapter.clientSize / 2) - ((sliderElement.firstElementChild as HTMLElement).lastElementChild?.clientWidth / 2);\n    expect(adapter.getCentralizerEndSize()).toBe(expectedSize);\n  });\n\n  it('getHammerVelocity should return the correct velocity', () => {\n    const e = { velocityX: 2.5 };\n    expect(adapter.getHammerVelocity(e)).toBe(e.velocityX);\n  });\n\n  it('getHammerValue should return the correct scroll options', () => {\n    const value = 100;\n    const e = { deltaX: 50 };\n    const scrollToOptions = adapter.getHammerValue(value, e, 'smooth');\n    expect(scrollToOptions).toEqual({\n      behavior: 'smooth',\n      left: value - e.deltaX\n    });\n  });\n});\n\ndescribe('VerticalAdapter', () => {\n  let adapter: VerticalAdapter;\n  let fixture: ComponentFixture<TestComponent>;\n  let component: TestComponent;\n  let sliderElement: HTMLElement;\n  let contentElement: HTMLElement;\n  let config: GalleryConfig;\n\n  beforeEach(async () => {\n    await TestBed.configureTestingModule({\n      imports: [TestComponent],\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(TestComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges(); // Trigger change detection to render the template\n\n    sliderElement = component.slider().nativeElement;\n    contentElement = component.content().nativeElement;\n    config = {};\n    adapter = new VerticalAdapter(sliderElement, config);\n\n    sliderElement.style.height = '500px';\n    sliderElement.style.overflowX = 'auto';\n\n    contentElement.style.height = '1000px';\n\n    config = {};\n    adapter = new VerticalAdapter(sliderElement, config);\n  });\n\n  it('should create an instance', () => {\n    expect(adapter).toBeTruthy();\n  });\n\n  it('should have the correct hammerDirection', () => {\n    expect(adapter.hammerDirection).toBe(DIRECTION_UP | DIRECTION_DOWN);\n  });\n\n  it('should have the correct scrollSnapType', () => {\n    expect(adapter.scrollSnapType).toBe('y mandatory');\n  });\n\n  it('should return the correct scrollValue', () => {\n    sliderElement.scrollTop = 100;\n    expect(adapter.scrollValue).toBe(100);\n  });\n\n  it('should return the correct clientSize', () => {\n    expect(adapter.clientSize).toBe(500);\n  });\n\n  it('should return the correct isContentLessThanContainer value', () => {\n    expect(adapter.isContentLessThanContainer).toBe(false); // Content is taller than container in beforeEach setup\n\n    (sliderElement.firstElementChild as HTMLElement).style.height = '400px';\n    expect(adapter.isContentLessThanContainer).toBe(true); // Content is now less than container\n  });\n\n  it('getScrollToValue should return the correct scroll options', () => {\n    const target = document.createElement('div');\n    target.style.height = '100px';\n    sliderElement.appendChild(target);\n\n    const scrollToOptions = adapter.getScrollToValue(target, 'smooth');\n    const expectedPosition = target.offsetTop - ((adapter.clientSize - target.clientHeight) / 2);\n    expect(scrollToOptions).toEqual({\n      behavior: 'smooth',\n      top: expectedPosition\n    });\n  });\n\n  it('getRootMargin should return the correct root margin', () => {\n    expect(adapter.getRootMargin()).toBe('0px 1000px 0px 1000px');\n  });\n\n  it('getElementRootMargin should return the correct element root margin', () => {\n    const viewport = document.createElement('div');\n    viewport.style.height = '500px';\n    const el = document.createElement('div');\n    el.style.height = '100px';\n\n    const rootMargin = -1 * ((viewport.clientHeight - el.clientHeight) / 2) + 1;\n    expect(adapter.getElementRootMargin(viewport, el)).toBe(`${ rootMargin }px 0px ${ rootMargin }px 0px`);\n  });\n\n  it('getCentralizerStartSize should return the correct size when content is less than container', () => {\n    (sliderElement.firstElementChild as HTMLElement).style.height = '400px';\n    const size = adapter.clientSize - (sliderElement.firstElementChild as HTMLElement).clientHeight;\n    expect(adapter.getCentralizerStartSize()).toBe(size / 2);\n  });\n\n  it('getCentralizerStartSize should return the correct size when content is greater than or equal to container', () => {\n    const expectedSize = (adapter.clientSize / 2) - ((sliderElement.firstElementChild as HTMLElement).firstElementChild?.clientHeight / 2);\n    expect(adapter.getCentralizerStartSize()).toBe(expectedSize);\n  });\n\n  it('getCentralizerEndSize should return the correct size when content is less than container', () => {\n    (sliderElement.firstElementChild as HTMLElement).style.height = '400px';\n    const size = adapter.clientSize - (sliderElement.firstElementChild as HTMLElement).clientHeight;\n    expect(adapter.getCentralizerEndSize()).toBe(size / 2);\n  });\n\n  it('getCentralizerEndSize should return the correct size when content is greater than or equal to container', () => {\n    const expectedSize = (adapter.clientSize / 2) - ((sliderElement.firstElementChild as HTMLElement).lastElementChild?.clientHeight / 2);\n    expect(adapter.getCentralizerEndSize()).toBe(expectedSize);\n  });\n\n  it('getHammerVelocity should return the correct velocity', () => {\n    const e = { velocityY: 2.5 };\n    expect(adapter.getHammerVelocity(e)).toBe(e.velocityY);\n  });\n\n  it('getHammerValue should return the correct scroll options', () => {\n    const value = 100;\n    const e = { deltaY: 50 };\n    const scrollToOptions = adapter.getHammerValue(value, e, 'smooth');\n    expect(scrollToOptions).toEqual({\n      behavior: 'smooth',\n      top: value - e.deltaY\n    });\n  });\n});\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/adapters/main-adapters.ts",
    "content": "import { GalleryConfig } from '../../models/config.model';\nimport { SliderAdapter } from './base-adapter';\nimport { SmoothScrollOptions } from '../../smooth-scroll';\n\n/**\n * A clone of HammerJs constants\n */\nexport const DIRECTION_LEFT: number = 2;\nexport const DIRECTION_RIGHT: number = 4;\nexport const DIRECTION_UP: number = 8;\nexport const DIRECTION_DOWN: number = 16;\n\nexport class HorizontalAdapter implements SliderAdapter {\n\n  readonly hammerDirection: number = DIRECTION_LEFT | DIRECTION_RIGHT;\n\n  readonly scrollSnapType: string = 'x mandatory';\n\n  get scrollValue(): number {\n    return this.slider.scrollLeft;\n  }\n\n  get clientSize(): number {\n    return this.slider.clientWidth;\n  }\n\n  get isContentLessThanContainer(): boolean {\n    return this.clientSize >= this.slider.firstElementChild.clientWidth;\n  }\n\n  constructor(public slider: HTMLElement, public config: GalleryConfig) {\n  }\n\n  getScrollToValue(target: HTMLElement, behavior: ScrollBehavior): SmoothScrollOptions {\n    const position: number = target.offsetLeft - ((this.clientSize - target.clientWidth) / 2);\n    return {\n      behavior,\n      start: position\n    };\n  }\n\n  getRootMargin(): string {\n    // return `1000px 1px 1000px 1px`;\n    return `1000px 0px 1000px 0px`;\n  }\n\n  getElementRootMargin(viewport: HTMLElement, el: HTMLElement): string {\n    const rootMargin: number = -1 * ((viewport.clientWidth - el.clientWidth) / 2) + 1;\n    return `0px ${ rootMargin }px 0px ${ rootMargin }px`;\n  }\n\n  getCentralizerStartSize(): number {\n    if (this.isContentLessThanContainer) {\n      const size: number = this.clientSize - this.slider.firstElementChild.clientWidth;\n      return size / 2;\n    }\n    return (this.clientSize / 2) - (this.slider.firstElementChild.firstElementChild?.clientWidth / 2);\n  }\n\n  getCentralizerEndSize(): number {\n    if (this.isContentLessThanContainer) {\n      const size: number = this.clientSize - this.slider.firstElementChild.clientWidth;\n      return size / 2;\n    }\n    return (this.clientSize / 2) - (this.slider.firstElementChild.lastElementChild?.clientWidth / 2);\n  }\n\n  getHammerVelocity(e: any): number {\n    return e.velocityX;\n  }\n\n  getHammerValue(value: number, e: any, behavior: ScrollBehavior): ScrollToOptions {\n    return {\n      behavior,\n      left: value - e.deltaX\n    };\n  }\n\n  // getDraggingProperty(e: MouseEvent): number {\n  //   return e.clientX;\n  // }\n\n  // getDraggingValue(value: number, delta: number, behavior: ScrollBehavior): ScrollToOptions {\n  //   return {\n  //     behavior,\n  //     left: value - delta\n  //   };\n  // }\n}\n\nexport class VerticalAdapter implements SliderAdapter {\n\n  readonly hammerDirection: number = DIRECTION_UP | DIRECTION_DOWN;\n\n  readonly scrollSnapType: string = 'y mandatory';\n\n  get scrollValue(): number {\n    return this.slider.scrollTop;\n  }\n\n  get clientSize(): number {\n    return this.slider.clientHeight;\n  }\n\n  get isContentLessThanContainer(): boolean {\n    return this.clientSize >= this.slider.firstElementChild.clientHeight;\n  }\n\n  constructor(public slider: HTMLElement, public config: GalleryConfig) {\n  }\n\n  getScrollToValue(target: HTMLElement, behavior: ScrollBehavior): SmoothScrollOptions {\n    const position: number = target.offsetTop - ((this.clientSize - target.clientHeight) / 2);\n    return {\n      behavior,\n      top: position\n    };\n  }\n\n  getRootMargin(): string {\n    return `0px 1000px 0px 1000px`;\n  }\n\n  getElementRootMargin(viewport: HTMLElement, el: HTMLElement): string {\n    const rootMargin: number = -1 * ((viewport.clientHeight - el.clientHeight) / 2) + 1;\n    return `${ rootMargin }px 0px ${ rootMargin }px 0px`;\n  }\n\n  getCentralizerStartSize(): number {\n    if (this.isContentLessThanContainer) {\n      const size = this.clientSize - this.slider.firstElementChild.clientHeight;\n      return size / 2;\n    }\n    return (this.clientSize / 2) - (this.slider.firstElementChild.firstElementChild?.clientHeight / 2);\n  }\n\n  getCentralizerEndSize(): number {\n    if (this.isContentLessThanContainer) {\n      const size = this.clientSize - this.slider.firstElementChild.clientHeight;\n      return size / 2;\n    }\n    return (this.clientSize / 2) - (this.slider.firstElementChild.lastElementChild?.clientHeight / 2);\n  }\n\n  getHammerVelocity(e: any): number {\n    return e.velocityY;\n  }\n\n  getHammerValue(value: number, e: any, behavior: ScrollBehavior): ScrollToOptions {\n    return {\n      behavior,\n      top: value - e.deltaY\n    };\n  }\n\n  // getDraggingProperty(e: MouseEvent): number {\n  //   return e.clientY;\n  // }\n\n  // getDraggingValue(value: number, delta: number, behavior: ScrollBehavior): ScrollToOptions {\n  //   return {\n  //     behavior,\n  //     top: value - delta\n  //   };\n  // }\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/gallery-slider.component.ts",
    "content": "import {\n  Component,\n  inject,\n  output,\n  input,\n  viewChild,\n  InputSignal,\n  TemplateRef,\n  OutputEmitterRef,\n  ChangeDetectionStrategy\n} from '@angular/core';\nimport { GalleryRef } from '../services/gallery-ref';\nimport { SmoothScroll } from '../smooth-scroll';\nimport { HammerSliding } from '../gestures/hammer-sliding.directive';\nimport { IntersectionSensor } from '../observers/intersection-sensor.directive';\nimport { SliderItem } from './slider-item/slider-item';\nimport { ScrollSnapType } from '../services/scroll-snap-type';\nimport { ResizeSensor } from '../services/resize-sensor';\nimport { SliderComponent } from './slider/slider';\nimport { GalleryItemContext } from '../directives/gallery-item-def.directive';\nimport { GalleryItemData } from '../templates/items.model';\n\n@Component({\n  selector: 'gallery-slider',\n  template: `\n    <g-slider [orientation]=\"galleryRef.config().orientation\"\n              [autosize]=\"galleryRef.config().itemAutosize\"\n              [centralized]=\"galleryRef.config().centralized\"\n              resizeSensor\n              smoothScroll\n              intersectionSensor\n              hammerSliding\n              scrollSnapType>\n      <div class=\"g-slider-content\">\n        @for (item of galleryRef.items(); track i; let i = $index; let count = $count) {\n          <slider-item [data]=\"item\"\n                       [template]=\"template()\"\n                       [currIndex]=\"galleryRef.currIndex()\"\n                       [index]=\"i\"\n                       [count]=\"count\"\n                       (click)=\"itemClick.emit(i)\"/>\n        }\n      </div>\n\n      @if (galleryRef.config().debug) {\n        <div class=\"g-slider-debug\">\n          <div class=\"g-slider-resizing\">RESIZING</div>\n          <div class=\"g-slider-scrolling\">SCROLLING</div>\n          <div class=\"g-slider-sliding\">SLIDING</div>\n          <div class=\"g-slider-observed\">CURRENT: {{ galleryRef.currIndex() }}</div>\n        </div>\n      }\n    </g-slider>\n    <ng-content/>\n  `,\n  styleUrl: './gallery-slider.scss',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  imports: [\n    ResizeSensor,\n    IntersectionSensor,\n    SmoothScroll,\n    HammerSliding,\n    ScrollSnapType,\n    SliderItem,\n    SliderComponent\n  ]\n})\nexport class GallerySliderComponent {\n\n  readonly galleryRef: GalleryRef = inject(GalleryRef);\n\n  // TODO: Allow auto-height to access resize sensor\n  resizeSensor = viewChild(ResizeSensor);\n\n  template: InputSignal<TemplateRef<GalleryItemContext<GalleryItemData>>> = input<TemplateRef<GalleryItemContext<GalleryItemData>>>();\n\n  /** Stream that emits when thumb is clicked */\n  itemClick: OutputEmitterRef<number> = output<number>();\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/gallery-slider.scss",
    "content": ":host {\n  position: relative;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  overflow: hidden;\n  order: 1;\n  flex: 1;\n}\n\n.g-slider {\n  display: flex;\n  align-items: center;\n  transition: var(--g-height-transition);\n  min-height: 100%;\n  min-width: 100%;\n  max-height: 100%;\n  max-width: 100%;\n  height: var(--slider-height, 100%);\n  width: var(--slider-width, 100%);\n\n  //&.g-resizing {\n  //  // Changes the height of the slider to match the active item height\n  //  height: var(--slider-auto-height, 100%);\n  //}\n\n  overflow: var(--slider-overflow);\n  scroll-snap-type: var(--slider-scroll-snap-type);\n  scroll-snap-stop: always;\n  flex-direction: var(--slider-flex-direction);\n\n  scrollbar-width: none;\n\n  &[orientation='horizontal'] {\n    --g-item-width: var(--slider-width);\n    //--g-item-height: 100%;\n\n    &[autosize='true'] {\n      --g-item-width: auto;\n    }\n  }\n\n  &[orientation='vertical'] {\n    //--g-item-width: 100%;\n    --g-item-height: var(--slider-height);\n\n    &[autoSize='true'] {\n      --g-item-height: auto;\n    }\n  }\n\n  &::-webkit-scrollbar {\n    display: none;\n  }\n\n  //&.g-resizing {\n  //  ::ng-deep {\n  //    gallery-item {\n  //      visibility: hidden;\n  //    }\n  //  }\n  //}\n\n  //&.g-sliding, &.g-scrolling {\n  &.g-sliding {\n    // Disable mouse click on gallery items/thumbnails when the slider is being dragged using the mouse\n    .g-slider-content {\n      pointer-events: none;\n    }\n  }\n\n  &[centralised=\"true\"] {\n    &:before, &:after {\n      content: '';\n    }\n\n    &:before {\n      flex: 0 0 var(--centralize-start-size);\n    }\n\n    &:after {\n      flex: 0 0 var(--centralize-end-size);\n    }\n  }\n}\n\n.g-slider-content {\n  // Never set min-width to 100%, content wrapper should always match content size to measure centralize size\n  flex: 0 0 auto;\n  display: flex;\n  align-items: center;\n  //gap: 1px;\n  width: var(--slider-content-width, unset);\n  height: var(--slider-content-height, unset);\n  flex-direction: var(--slider-flex-direction);\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/slider/slider.spec.ts",
    "content": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { By } from '@angular/platform-browser';\nimport { DebugElement } from '@angular/core';\nimport { SliderItem } from '../slider-item/slider-item';\nimport { TestComponent } from '../../tests/common';\nimport { SliderComponent } from './slider';\nimport { HorizontalAdapter, VerticalAdapter } from '../adapters';\n\ndescribe('Gallery slider', () => {\n  let fixture: ComponentFixture<TestComponent>;\n  let component: TestComponent;\n  let sliderComponent: SliderComponent;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [\n        NoopAnimationsModule,\n        TestComponent\n      ],\n      providers: [\n        { provide: ComponentFixtureAutoDetect, useValue: true }\n      ]\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(TestComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n\n    const sliderComponentElement: DebugElement = fixture.debugElement.query(By.directive(SliderComponent));\n    sliderComponent = sliderComponentElement.componentInstance;\n  });\n\n  it('should create slider component with default class and attributes', () => {\n    expect(sliderComponent).toBeTruthy();\n    expect(sliderComponent.nativeElement.classList).toContain('g-slider');\n    expect(sliderComponent.nativeElement.getAttribute('centralised')).toBe('false');\n    expect(sliderComponent.nativeElement.getAttribute('orientation')).toBe('horizontal');\n    expect(sliderComponent.nativeElement.getAttribute('autosize')).toBe('false');\n  });\n\n  it('should use horizontal adapter when orientation config specifies \"horizontal\"', () => {\n    component.gallery().galleryRef.setConfig({\n      orientation: 'horizontal'\n    });\n    fixture.detectChanges();\n\n    expect(sliderComponent.nativeElement.getAttribute('orientation')).toBe('horizontal');\n    expect(sliderComponent.adapter()).toBeInstanceOf(HorizontalAdapter);\n  });\n\n  it('should use vertical adapter when orientation config specifies \"vertical\"', () => {\n    component.gallery().galleryRef.setConfig({\n      orientation: 'vertical'\n    });\n    fixture.detectChanges();\n\n    expect(sliderComponent.nativeElement.getAttribute('orientation')).toBe('vertical');\n    expect(sliderComponent.adapter()).toBeInstanceOf(VerticalAdapter);\n  });\n\n  it('should render the items loaded in the gallery', () => {\n    const items: DebugElement[] = fixture.debugElement.queryAll(By.directive(SliderItem));\n    expect(items.length).toBe(component.gallery().galleryRef.items().length);\n  });\n});\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/slider/slider.ts",
    "content": "import {\n  Component,\n  inject,\n  computed,\n  contentChildren,\n  booleanAttribute,\n  input,\n  Signal,\n  ElementRef,\n  InputSignal,\n  WritableSignal,\n  ChangeDetectionStrategy,\n  InputSignalWithTransform\n} from '@angular/core';\nimport { GalleryRef } from '../../services/gallery-ref';\nimport { GalleryConfig } from '../../models/config.model';\nimport { ORIENTATION } from '../../models/constants';\nimport { HorizontalAdapter, SliderAdapter, VerticalAdapter } from '../adapters';\nimport { SliderItem } from '../slider-item/slider-item';\n\n@Component({\n  host: {\n    '[class.g-slider]': 'true',\n    '[attr.centralised]': 'centralized()',\n    '[attr.orientation]': 'orientation()',\n    '[attr.autosize]': 'autosize()',\n  },\n  selector: 'g-slider',\n  template: '<ng-content/>',\n  changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class SliderComponent {\n\n  readonly galleryRef: GalleryRef = inject(GalleryRef);\n\n  readonly nativeElement: HTMLElement = inject(ElementRef<HTMLElement>).nativeElement;\n\n  readonly orientation: InputSignal<ORIENTATION> = input<ORIENTATION>();\n\n  readonly autosize: InputSignal<boolean> = input<boolean>();\n\n  readonly centralized: InputSignal<boolean> = input<boolean>();\n\n  readonly items: Signal<ReadonlyArray<SliderItem>> = contentChildren<SliderItem>(SliderItem, { descendants: true });\n\n  isThumbs: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(false, {\n    transform: booleanAttribute\n  });\n\n  readonly adapter: Signal<SliderAdapter> = computed(() => {\n    const config: GalleryConfig = this.galleryRef.config();\n    return this.orientation() === ORIENTATION.Horizontal\n      ? new HorizontalAdapter(this.nativeElement, config)\n      : new VerticalAdapter(this.nativeElement, config);\n  });\n\n  get visibleEntries(): WritableSignal<Record<number, IntersectionObserverEntry>> {\n    if (this.isThumbs()) {\n      return this.galleryRef.visibleThumbs;\n    }\n    return this.galleryRef.visibleItems;\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/slider-item/slider-item.scss",
    "content": ":host {\n  cursor: var(--g-item-cursor);\n  height: var(--g-item-height);\n  width: var(--g-item-width);\n  max-height: var(--g-item-max-height);\n  max-width: var(--slider-width);\n  //max-width: var(--g-item-width);\n  z-index: 10;\n  position: relative;\n  overflow: hidden;\n  display: flex;\n  flex-direction: column;\n  flex: 0 0 auto;\n  scroll-snap-align: center;\n  //align-self: center;\n\n  // Disable highlighting the elements on mouse move or click\n  user-select: none;\n  -webkit-user-drag: none;\n  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n\n  // In itemAutoSize is true, item size will be zero until it is loaded, fallback to the slider size\n  &[itemState='loading'] {\n    width: var(--slider-width);\n    height: var(--slider-height);\n  }\n\n  > * {\n    height: 100%;\n  }\n\n  ::ng-deep {\n    video, iframe {\n      width: 100%;\n      height: 100%;\n    }\n  }\n  ::ng-deep {\n    img.g-image-item {\n      object-fit: var(--image-object-fit);\n      width: 100%;\n      height: 100%;\n      pointer-events: none;\n      max-height: 100%;\n      max-width: 100%;\n    }\n  }\n}\n\ngallery-image {\n  width: var(--g-item-width);\n  height: var(--g-item-height);\n}\n\n.g-template {\n  position: absolute;\n  z-index: 10;\n  left: 0;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  color: white;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex-direction: column;\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/slider-item/slider-item.spec.ts",
    "content": "import { ComponentFixture, TestBed } from '@angular/core/testing';\nimport { GalleryItemData } from 'ng-gallery';\nimport { SliderItem } from './slider-item';\n\ndescribe('GalleryItemComponent', () => {\n  let component: SliderItem;\n  let fixture: ComponentFixture<SliderItem>;\n\n  beforeEach(async () => {\n    await TestBed.configureTestingModule({\n      imports: [SliderItem],\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(SliderItem);\n    component = fixture.componentInstance;\n    // Assume the item has an image\n    component.isItemContainImage = true;\n  });\n\n  it('should create the component', () => {\n    expect(component).toBeTruthy();\n  });\n\n  it('should set inputs correctly', () => {\n    const mockData: GalleryItemData = { src: 'test.jpg' };\n    fixture.componentRef.setInput('data', mockData);\n    fixture.componentRef.setInput('count', 5);\n    fixture.componentRef.setInput('currIndex', 2);\n    fixture.componentRef.setInput('index', 2);\n    fixture.detectChanges();\n\n    expect(component.data()).toEqual(mockData);\n    expect(component.count()).toBe(5);\n    expect(component.currIndex()).toBe(2);\n    expect(component.index()).toBe(2);\n  });\n\n  it('should compute active state correctly', () => {\n    fixture.componentRef.setInput('currIndex', 2);\n    fixture.componentRef.setInput('index', 2);\n    fixture.detectChanges();\n    expect(component.active()).toBeTrue();\n\n    fixture.componentRef.setInput('currIndex', 1);\n    fixture.detectChanges();\n    expect(component.active()).toBeFalse();\n  });\n\n  it('should compute itemContext correctly', () => {\n    const mockData: GalleryItemData = { src: 'test.jpg' };\n    fixture.componentRef.setInput('data', mockData);\n    fixture.componentRef.setInput('index', 0);\n    fixture.componentRef.setInput('count', 3);\n    fixture.componentRef.setInput('currIndex', 1);\n    fixture.detectChanges();\n\n    const context = component.itemContext();\n    expect(context.index).toBe(0);\n    expect(context.count).toBe(3);\n    expect(context.first).toBeTrue();\n    expect(context.last).toBeFalse();\n    expect(context.active).toBeFalse();\n  });\n\n  it('should update state if item does not contain an image', () => {\n    component.isItemContainImage = false;\n    component.ngAfterViewInit();\n    fixture.detectChanges();\n    expect(component.state()).toBe('success');\n  });\n\n  it('should set correct attributes and class', () => {\n    fixture.componentRef.setInput('index', 2);\n    fixture.componentRef.setInput('count', 3);\n    fixture.componentRef.setInput('currIndex', 2);\n    component.state.set('loading');\n    fixture.detectChanges();\n\n    const element: HTMLElement = fixture.debugElement.nativeElement;\n    expect(element.getAttribute('galleryIndex')).toBe('2');\n    expect(element.getAttribute('itemState')).toBe('loading');\n    expect(element.classList.contains('g-active-item')).toBeTrue();\n  });\n});\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/slider/slider-item/slider-item.ts",
    "content": "import {\n  Component,\n  inject,\n  signal,\n  computed,\n  input,\n  Signal,\n  Injector,\n  ElementRef,\n  InputSignal,\n  TemplateRef,\n  AfterViewInit,\n  WritableSignal,\n  ChangeDetectionStrategy\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { toObservable } from '@angular/core/rxjs-interop';\nimport { Observable } from 'rxjs';\nimport { GalleryItemContext } from '../../directives/gallery-item-def.directive';\nimport { GalleryItemData, } from '../../templates/items.model';\nimport { ItemState } from '../../models/item.model';\n\n@Component({\n  selector: 'slider-item',\n  host: {\n    '[attr.itemState]': 'state()',\n    '[attr.galleryIndex]': 'index()',\n    '[class.g-active-item]': 'active()',\n    // TODO: need to make a linkedSignal to determine the visible flag visibleThumb from visibleItems with the index\n    '[class.g-visible-item]': 'visible()'\n  },\n  template: `\n    <ng-container [ngTemplateOutlet]=\"template()\"\n                  [ngTemplateOutletContext]=\"itemContext()\"\n                  [ngTemplateOutletInjector]=\"injector\"/>\n  `,\n  styleUrl: './slider-item.scss',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  imports: [NgTemplateOutlet]\n})\nexport class SliderItem implements AfterViewInit {\n\n  readonly injector: Injector = inject(Injector);\n\n  readonly nativeElement: HTMLElement = inject(ElementRef<HTMLElement>).nativeElement;\n\n  /** Item's index in the gallery */\n  index: InputSignal<number> = input<number>();\n\n  visible: InputSignal<boolean> = input<boolean>();\n\n  // readonly visible: Signal<boolean> = computed(() => {\n  //   return !!this.galleryRef.visibleItems()[this.index()]\n  // });\n\n  /** A stream that indicates that the height was emitted after the image is loaded, used only for gallery image types */\n  state: WritableSignal<ItemState> = signal<ItemState>('loading');\n\n  readonly state$: Observable<ItemState> = toObservable(this.state);\n  /** The number of total slider-item */\n  count: InputSignal<number> = input<number>();\n\n  template: InputSignal<TemplateRef<GalleryItemContext<GalleryItemData>>> = input<TemplateRef<GalleryItemContext<GalleryItemData>>>();\n\n  /** Gallery current index */\n  currIndex: InputSignal<number> = input<number>();\n\n  /** Item's data, this object contains the data required to display the content (e.g. src path) */\n  data: InputSignal<GalleryItemData> = input<GalleryItemData>();\n\n  active: Signal<boolean> = computed(() => this.index() === this.currIndex());\n\n  itemContext: Signal<GalleryItemContext<GalleryItemData>> = computed(() => {\n    return {\n      $implicit: this.data(),\n      index: this.index(),\n      active: this.active(),\n      count: this.count(),\n      first: this.index() === 0,\n      last: this.index() === this.count() - 1\n    };\n  });\n\n  /** A flag that indicates if the item is type of image, it can be a custom template by the user,\n   * The img recognizer directive will set it to true*/\n  isItemContainImage: boolean;\n\n  ngAfterViewInit(): void {\n    // If item does not contain an image, then set the state to DONE\n    if (!this.isItemContainImage) {\n      this.state.set('success');\n    }\n  }\n}\n\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/smooth-scroll/bezier-easing.spec.ts",
    "content": "import bezier from './bezier-easing';\n\ndescribe('Bezier Easing Function', () => {\n  it('should return a function', () => {\n    const easingFunction = bezier(0.25, 0.1, 0.25, 1.0);\n    expect(typeof easingFunction).toBe('function');\n  });\n\n  it('should throw an error if x values are out of range', () => {\n    expect(() => bezier(-0.5, 0, 1.5, 1)).toThrowError('bezier x values must be in [0, 1] range');\n  });\n\n  it('should return linear easing when control points form a straight line', () => {\n    const linear = bezier(0, 0, 1, 1);\n    expect(linear(0)).toBe(0);\n    expect(linear(0.5)).toBe(0.5);\n    expect(linear(1)).toBe(1);\n  });\n\n  it('should return values between 0 and 1 for valid input', () => {\n    const easingFunction = bezier(0.42, 0, 0.58, 1);\n    expect(easingFunction(0)).toBe(0);\n    expect(easingFunction(1)).toBe(1);\n    expect(easingFunction(0.5)).toBeGreaterThan(0);\n    expect(easingFunction(0.5)).toBeLessThan(1);\n  });\n\n  it('should correctly approximate a cubic bezier curve', () => {\n    const easingFunction = bezier(0.42, 0, 0.58, 1);\n    const result = easingFunction(0.25);\n    expect(result).toBeGreaterThan(0);\n    expect(result).toBeLessThan(1);\n  });\n\n  it('should correctly handle extreme bezier curves', () => {\n    const easeIn = bezier(0.9, 0, 1, 1);\n    const easeOut = bezier(0, 0, 0.1, 1);\n\n    // Fix: easeIn should be less than easeOut at x = 0.5\n    expect(easeIn(0.5)).toBeLessThan(easeOut(0.5));\n  });\n});\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/smooth-scroll/bezier-easing.ts",
    "content": "/**\n * https://github.com/gre/bezier-easing\n * BezierEasing - use bezier curve for transition easing function\n * by Gaëtan Renaudeau 2014 - 2015 – MIT License\n */\n\n// These values are established by empiricism with tests (tradeoff: performance VS precision)\nconst NEWTON_ITERATIONS: number = 4;\nconst NEWTON_MIN_SLOPE: number = 0.001;\nconst SUBDIVISION_PRECISION: number = 0.0000001;\nconst SUBDIVISION_MAX_ITERATIONS: number = 10;\n\nconst kSplineTableSize: number = 11;\nconst kSampleStepSize: number = 1.0 / (kSplineTableSize - 1.0);\n\nfunction A(aA1: number, aA2: number): number {\n  return 1.0 - 3.0 * aA2 + 3.0 * aA1;\n}\n\nfunction B(aA1: number, aA2: number): number {\n  return 3.0 * aA2 - 6.0 * aA1;\n}\n\nfunction C(aA1: number): number {\n  return 3.0 * aA1;\n}\n\n// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.\nfunction calcBezier(aT: number, aA1: number, aA2: number): number {\n  return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT;\n}\n\n// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.\nfunction getSlope(aT: number, aA1: number, aA2: number): number {\n  return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1);\n}\n\nfunction binarySubdivide(aX: number, aA: number, aB: number, mX1: number, mX2: number) {\n  let currentX, currentT, i: number = 0;\n  do {\n    currentT = aA + (aB - aA) / 2.0;\n    currentX = calcBezier(currentT, mX1, mX2) - aX;\n    if (currentX > 0.0) {\n      aB = currentT;\n    } else {\n      aA = currentT;\n    }\n  } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS);\n  return currentT;\n}\n\nfunction newtonRaphsonIterate(aX: number, aGuessT: number, mX1: number, mX2: number): number {\n  for (let i: number = 0; i < NEWTON_ITERATIONS; ++i) {\n    const currentSlope: number = getSlope(aGuessT, mX1, mX2);\n    if (currentSlope === 0.0) {\n      return aGuessT;\n    }\n    const currentX: number = calcBezier(aGuessT, mX1, mX2) - aX;\n    aGuessT -= currentX / currentSlope;\n  }\n  return aGuessT;\n}\n\nfunction LinearEasing(x: number): number {\n  return x;\n}\n\nexport default function bezier(mX1: number, mY1: number, mX2: number, mY2: number) {\n  if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) {\n    throw new Error('bezier x values must be in [0, 1] range');\n  }\n\n  if (mX1 === mY1 && mX2 === mY2) {\n    return LinearEasing;\n  }\n\n  // Precompute samples table\n  const sampleValues:Float32Array = new Float32Array(kSplineTableSize);\n  for (let i: number = 0; i < kSplineTableSize; ++i) {\n    sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2);\n  }\n\n  function getTForX(aX: number): number {\n    let intervalStart: number = 0.0;\n    let currentSample: number = 1;\n    const lastSample: number = kSplineTableSize - 1;\n\n    for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) {\n      intervalStart += kSampleStepSize;\n    }\n    --currentSample;\n\n    // Interpolate to provide an initial guess for t\n    const dist: number = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]);\n    const guessForT: number = intervalStart + dist * kSampleStepSize;\n\n    const initialSlope: number = getSlope(guessForT, mX1, mX2);\n    if (initialSlope >= NEWTON_MIN_SLOPE) {\n      return newtonRaphsonIterate(aX, guessForT, mX1, mX2);\n    } else if (initialSlope === 0.0) {\n      return guessForT;\n    } else {\n      return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2);\n    }\n  }\n\n  return function BezierEasing(x: number): number {\n    // Because JavaScript number are imprecise, we should guarantee the extremes are right.\n    if (x === 0) {\n      return 0;\n    }\n    if (x === 1) {\n      return 1;\n    }\n    return calcBezier(getTForX(x), mY1, mY2);\n  };\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/smooth-scroll/index.ts",
    "content": "export * from './smooth-scroll.directive';\nexport * from './smooth-scroll.model';\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/smooth-scroll/smooth-scroll.directive.ts",
    "content": "import {\n  Directive,\n  inject,\n  signal,\n  effect,\n  untracked,\n  input,\n  NgZone,\n  ElementRef,\n  InputSignal,\n  WritableSignal,\n  EffectCleanupRegisterFn\n} from '@angular/core';\nimport { DOCUMENT } from '@angular/common';\nimport { Directionality } from '@angular/cdk/bidi';\nimport { _Bottom, _Left, _Right, _Top, _Without } from '@angular/cdk/scrolling';\nimport { getRtlScrollAxisType, RtlScrollAxisType } from '@angular/cdk/platform';\nimport {\n  Observable,\n  Subject,\n  Subscriber,\n  Subscription,\n  of,\n  take,\n  merge,\n  expand,\n  fromEvent,\n  switchMap,\n  takeUntil,\n  takeWhile,\n  finalize\n} from 'rxjs';\nimport BezierEasing from './bezier-easing';\nimport { SmoothScrollOptions, SmoothScrollStep, SmoothScrollToOptions } from './index';\nimport { GalleryRef } from '../services/gallery-ref';\nimport { IndexChange } from '../models/slider.model';\nimport { SliderComponent } from '../slider/slider/slider';\nimport { HammerSliding } from '../gestures/hammer-sliding.directive';\n\n@Directive({\n  selector: '[smoothScroll]',\n  host: {\n    '[class.g-scrolling]': 'scrolling()'\n  }\n})\nexport class SmoothScroll {\n\n  private readonly galleryRef: GalleryRef = inject(GalleryRef);\n\n  private readonly slider: SliderComponent = inject(SliderComponent, { self: true });\n\n  private readonly hammerSlider: HammerSliding = inject(HammerSliding, { self: true });\n\n  private readonly _zone: NgZone = inject(NgZone);\n\n  private readonly _dir: Directionality = inject(Directionality);\n\n  private readonly _el: HTMLElement = inject(ElementRef<HTMLElement>).nativeElement;\n\n  private readonly _w: Window = inject(DOCUMENT).defaultView;\n\n  private readonly _scrollController: Subject<SmoothScrollStep> = new Subject<SmoothScrollStep>();\n\n  private readonly _finished: Subject<void> = new Subject<void>();\n\n  /**\n   * Timing method\n   */\n  private get _now(): () => number {\n    return this._w.performance?.now?.bind(this._w.performance) || Date.now;\n  }\n\n  private readonly interruptedByMouse$: Subject<void> = new Subject<void>();\n\n  scrolling: WritableSignal<boolean> = signal<boolean>(false);\n\n  disabled: InputSignal<boolean> = input<boolean>(false, { alias: 'smoothScroll' });\n\n  constructor() {\n    // This directive should not do anything if was used by gallery-thumbs and detached option is true\n    let indexChangeSub$: Subscription;\n    let scrollSub$: Subscription;\n\n    effect(() => {\n      if (!this.hammerSlider.sliding() || this.disabled()) return;\n      this.interruptedByMouse$.next();\n    });\n\n    effect((onCleanup: EffectCleanupRegisterFn) => {\n      if (this.disabled()) return;\n\n      untracked(() => {\n        this._zone.runOutsideAngular(() => {\n          indexChangeSub$ = this.galleryRef.indexChange.subscribe((change: IndexChange) => {\n            const el: HTMLElement = this.slider.items()[change.index]?.nativeElement;\n            const scrollBehavior: ScrollBehavior = change.behavior || this.galleryRef.config().scrollBehavior;\n\n            if (el) {\n              if (scrollBehavior === 'auto') {\n                // When setting initial index, the viewport isn't scrollable. we need to wait for the gallery to be rendered.\n                requestAnimationFrame(() => {\n                  const params: SmoothScrollOptions = this.slider.adapter().getScrollToValue(el, scrollBehavior);\n                  const options: SmoothScrollToOptions = this._prepareParams(params);\n                  this.scrollElement(options.left, options.top);\n                });\n              } else {\n                const params: SmoothScrollOptions = this.slider.adapter().getScrollToValue(el, scrollBehavior);\n                this.scrollTo(params);\n              }\n            }\n          });\n\n          scrollSub$ = this._scrollController.pipe(\n            switchMap((context: SmoothScrollStep) => {\n              this._zone.run(() => {\n                this.scrolling.set(true);\n              });\n\n              // Scroll each step recursively\n              return of(null).pipe(\n                expand(() => this._step(context).pipe(\n                  takeWhile((currContext: SmoothScrollStep) => this._isFinished(currContext)),\n                  takeUntil(this._finished)\n                )),\n                finalize(() => this.resetElement()),\n                takeUntil(this._interrupted()),\n              );\n            })\n          ).subscribe();\n        });\n\n        onCleanup(() => {\n          scrollSub$?.unsubscribe();\n          indexChangeSub$?.unsubscribe();\n        });\n      });\n    });\n  }\n\n  /**\n   * changes scroll position inside an element\n   */\n  scrollElement(x: number, y: number): void {\n    this._el.scrollLeft = x;\n    this._el.scrollTop = y;\n  }\n\n  private resetElement(): void {\n    this._zone.run(() => {\n      this.scrolling.set(false);\n    });\n  }\n\n  /**\n   * Checks if smooth scroll has reached, cleans up the smooth scroll stream and resolves its promise\n   */\n  private _isFinished(context: SmoothScrollStep): boolean {\n    if (context.currentX !== context.x || context.currentY !== context.y) {\n      return true;\n    }\n    this._finished.next();\n    return false;\n  }\n\n  /**\n   * Terminates an ongoing smooth scroll\n   */\n  private _interrupted(): Observable<Event | void> {\n    return merge(\n      this.interruptedByMouse$,\n      fromEvent(this._el, 'wheel', { passive: true, capture: true }),\n      fromEvent(this._el, 'touchmove', { passive: true, capture: true }),\n    ).pipe(take(1));\n  }\n\n  /**\n   * A function called recursively that, given a context, steps through scrolling\n   */\n  private _step(context: SmoothScrollStep): Observable<SmoothScrollStep> {\n    return new Observable((subscriber: Subscriber<SmoothScrollStep>) => {\n      let elapsed: number = (this._now() - context.startTime) / context.duration;\n\n      // avoid elapsed times higher than one\n      elapsed = elapsed > 1 ? 1 : elapsed;\n\n      // apply easing to elapsed time\n      const value: number = context.easing(elapsed);\n\n      context.currentX = context.startX + (context.x - context.startX) * value;\n      context.currentY = context.startY + (context.y - context.startY) * value;\n\n      this.scrollElement(context.currentX, context.currentY);\n      // Proceed to the step\n      requestAnimationFrame(() => {\n        subscriber.next(context);\n        subscriber.complete();\n      });\n    });\n  }\n\n  private _applyScrollToOptions(options: SmoothScrollToOptions): void {\n    const context: SmoothScrollStep = {\n      scrollable: this._el,\n      startTime: this._now(),\n      startX: this._el.scrollLeft,\n      startY: this._el.scrollTop,\n      x: options.left == null ? this._el.scrollLeft : ~~options.left,\n      y: options.top == null ? this._el.scrollTop : ~~options.top,\n      duration: options.duration,\n      easing: BezierEasing(options.easing.x1, options.easing.y1, options.easing.x2, options.easing.y2)\n    };\n\n    this._scrollController.next(context);\n  }\n\n  private _prepareParams(params: SmoothScrollOptions): SmoothScrollToOptions {\n    const isRtl: boolean = this._dir.value === 'rtl';\n    const rtlScrollAxisType: RtlScrollAxisType = getRtlScrollAxisType();\n\n    const options: SmoothScrollToOptions = {\n      ...params,\n      ...({\n        // Rewrite start & end offsets as right or left offsets.\n        left: params.left == null ? (isRtl ? params.end : params.start) : params.left,\n        right: params.right == null ? (isRtl ? params.start : params.end) : params.right\n      }),\n      duration: params.behavior === 'smooth' ? this.galleryRef.config().scrollDuration : 0,\n      easing: this.galleryRef.config().scrollEase,\n    };\n\n    // Rewrite the bottom offset as a top offset.\n    if (options.bottom != null) {\n      (options as _Without<_Bottom> & _Top).top = this._el.scrollHeight - this._el.clientHeight - options.bottom;\n    }\n\n    // Rewrite the right offset as a left offset.\n    if (isRtl && rtlScrollAxisType !== RtlScrollAxisType.NORMAL) {\n      if (options.left != null) {\n        (options as _Without<_Left> & _Right).right = this._el.scrollWidth - this._el.clientWidth - options.left;\n      }\n\n      if (rtlScrollAxisType === RtlScrollAxisType.INVERTED) {\n        options.left = options.right;\n      } else if (rtlScrollAxisType === RtlScrollAxisType.NEGATED) {\n        options.left = options.right ? -options.right : options.right;\n      }\n    } else {\n      if (options.right != null) {\n        (options as _Without<_Right> & _Left).left = this._el.scrollWidth - this._el.clientWidth - options.right;\n      }\n    }\n    return options;\n  }\n\n  /**\n   * Scrolls to the specified offsets. This is a normalized version of the browser's native scrollTo\n   * method, since browsers are not consistent about what scrollLeft means in RTL. For this method\n   * left and right always refer to the left and right side of the scrolling container irrespective\n   * of the layout direction. start and end refer to left and right in an LTR context and vice versa\n   * in an RTL context.\n   * @param params specified the offsets to scroll to.\n   */\n  scrollTo(params: SmoothScrollOptions): void {\n    const options: SmoothScrollToOptions = this._prepareParams(params);\n    return this._applyScrollToOptions(options);\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/smooth-scroll/smooth-scroll.model.ts",
    "content": "import { _XAxis, _YAxis } from '@angular/cdk/scrolling';\n\nexport type SmoothScrollOptions = Partial<Pick<_XAxis, keyof _XAxis> & Pick<_YAxis, keyof _YAxis>> & {\n  behavior?: ScrollBehavior;\n}\n\nexport type SmoothScrollToOptions = SmoothScrollOptions & {\n  duration?: number;\n  easing?: BezierEasingOptions;\n};\n\nexport interface SmoothScrollStep {\n  scrollable: HTMLElement;\n  startTime: number;\n  startX: number;\n  startY: number;\n  x: number;\n  y: number;\n  duration: number;\n  easing: (k: number) => number;\n  currentX?: number;\n  currentY?: number;\n}\n\nexport interface BezierEasingOptions {\n  x1: number;\n  y1: number;\n  x2: number;\n  y2: number;\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/smooth-scroll/smooth-scroll.spec.ts",
    "content": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { By } from '@angular/platform-browser';\nimport { DebugElement } from '@angular/core';\nimport { GalleryRef } from 'ng-gallery';\nimport { afterTimeout, TestComponent } from '../tests/common';\nimport { SmoothScroll, SmoothScrollOptions } from './index';\nimport { filter, firstValueFrom, Observable } from 'rxjs';\n\ndescribe('Smooth scroll directive', () => {\n  let fixture: ComponentFixture<TestComponent>;\n  let nativeElement: HTMLElement;\n  let smoothScrollDirective: SmoothScroll;\n  let galleryRef: GalleryRef;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [\n        NoopAnimationsModule,\n        TestComponent\n      ],\n      providers: [\n        { provide: ComponentFixtureAutoDetect, useValue: true }\n      ]\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(TestComponent);\n    fixture.detectChanges();\n\n    const smoothScrollElement: DebugElement = fixture.debugElement.query(By.directive(SmoothScroll));\n    smoothScrollDirective = smoothScrollElement.injector.get(SmoothScroll);\n    nativeElement = smoothScrollElement.nativeElement;\n\n    galleryRef = smoothScrollElement.injector.get(GalleryRef);\n  });\n\n  it('should create [smoothScroll] directive', () => {\n    expect(smoothScrollDirective).toBeTruthy();\n  });\n\n  it('should toggle scrolling class with scrolling signal', async () => {\n    await firstValueFrom(galleryRef.afterItemsVisible);\n\n    expect(smoothScrollDirective.scrolling()).toBe(false);\n    expect(nativeElement.classList.contains('g-scrolling')).toBeFalse();\n\n    // Trigger index change\n    galleryRef.set(1, 'smooth');\n    fixture.detectChanges();\n    expect(smoothScrollDirective.scrolling()).toBe(true);\n    expect(nativeElement.classList.contains('g-scrolling')).toBeTrue();\n\n    const arrivedToNextItem$: Observable<any> = galleryRef.indexChanged.pipe(\n      filter((currIndex: number) => currIndex === 1)\n    );\n    await firstValueFrom(arrivedToNextItem$);\n\n    expect(smoothScrollDirective.scrolling()).toBe(false);\n    expect(nativeElement.classList.contains('g-scrolling')).toBeFalse();\n  });\n\n  it('should scroll instantly to target item on gallery index changes', async () => {\n    const scrollToSpy: jasmine.Spy = spyOn(smoothScrollDirective, 'scrollElement').and.callThrough();\n    await firstValueFrom(galleryRef.afterItemsVisible);\n\n    // Trigger index change\n    galleryRef.set(1, 'auto');\n\n    const arrivedToNextItem$: Observable<any> = galleryRef.indexChanged.pipe(\n      filter((currIndex: number) => currIndex === 1)\n    );\n    await firstValueFrom(arrivedToNextItem$);\n\n    expect(scrollToSpy).toHaveBeenCalledWith(500, undefined);\n    expect(galleryRef.currIndex()).toBe(1);\n    expect(smoothScrollDirective.scrolling()).toBe(false);\n  });\n\n  it('should scroll smoothly to target item on gallery index changes', async () => {\n    const scrollToSpy: jasmine.Spy = spyOn(smoothScrollDirective, 'scrollTo').and.callThrough();\n    await firstValueFrom(galleryRef.afterItemsVisible);\n\n    // Trigger index change\n    galleryRef.set(2, 'smooth');\n\n    expect(smoothScrollDirective.scrolling()).toBe(true);\n\n    const arrivedToNextItem$: Observable<any> = galleryRef.indexChanged.pipe(\n      filter((currIndex: number) => currIndex === 2)\n    );\n    await firstValueFrom(arrivedToNextItem$);\n\n    const pos: SmoothScrollOptions = {\n      start: 1000,\n      behavior: 'smooth'\n    };\n    expect(scrollToSpy).toHaveBeenCalledWith(pos);\n    expect(galleryRef.currIndex()).toBe(2);\n    expect(smoothScrollDirective.scrolling()).toBe(false);\n  });\n});\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/templates/gallery-iframe.component.ts",
    "content": "// import {\n//   Component,\n//   inject,\n//   computed,\n//   input,\n//   viewChild,\n//   Signal,\n//   ElementRef,\n//   InputSignal,\n//   ChangeDetectionStrategy\n// } from '@angular/core';\n// import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';\n//\n// @Component({\n//   selector: 'gallery-iframe',\n//   template: `\n//     @if (autoplay()) {\n//       <iframe #iframe\n//               [attr.loading]=\"loadingAttr()\"\n//               allowfullscreen\n//               allow\n//               style=\"border:none\"\n//               [src]=\"iframeSrc()\">\n//       </iframe>\n//     } @else {\n//       <iframe #iframe\n//               [attr.loading]=\"loadingAttr()\"\n//               allowfullscreen\n//               style=\"border:none\"\n//               [src]=\"iframeSrc()\">\n//       </iframe>\n//     }\n//   `,\n//   changeDetection: ChangeDetectionStrategy.OnPush\n// })\n// export class GalleryIframeComponent {\n//\n//   private _sanitizer: DomSanitizer = inject(DomSanitizer);\n//\n//   iframeSrc: Signal<SafeResourceUrl> = computed(() => {\n//     if (this.pause()) {\n//       return null;\n//     }\n//     return this._sanitizer.bypassSecurityTrustResourceUrl(this.src());\n//   });\n//\n//   src: InputSignal<string> = input<string>();\n//\n//   pause: InputSignal<boolean> = input<boolean>();\n//\n//   autoplay: InputSignal<boolean> = input<boolean>();\n//\n//   loadingAttr: InputSignal<'eager' | 'lazy'> = input();\n//\n//   iframe: Signal<ElementRef<HTMLIFrameElement>> = viewChild<ElementRef<HTMLIFrameElement>>('iframe');\n// }\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/templates/gallery-image.component.spec.ts",
    "content": "import { ComponentFixture, TestBed } from '@angular/core/testing';\nimport { provideNoopAnimations } from '@angular/platform-browser/animations';\nimport { DomSanitizer } from '@angular/platform-browser';\nimport { By } from '@angular/platform-browser';\nimport { signal } from '@angular/core';\nimport { GalleryRef, ImgRecognizer } from 'ng-gallery';\nimport { SliderItem } from '../slider/slider-item/slider-item';\nimport { GalleryImageComponent } from './gallery-image.component';\nimport { ImgManager } from '../utils/img-manager';\n\ndescribe('GalleryImageComponent', () => {\n  let component: GalleryImageComponent;\n  let fixture: ComponentFixture<GalleryImageComponent>;\n  let sanitizer: DomSanitizer;\n\n  beforeEach(async () => {\n    const sliderItem = jasmine.createSpyObj('SliderItem', ['index', 'state']);\n    (sliderItem['index'] as any) = jasmine.createSpy().and.callFake(() => signal(0));\n    (sliderItem['state'] as any) = jasmine.createSpy().and.callFake(() => signal('loading'));\n\n    await TestBed.configureTestingModule({\n      imports: [GalleryImageComponent],\n      providers: [\n        provideNoopAnimations(),\n        ImgRecognizer,\n        ImgManager,\n        GalleryRef,\n        { provide: SliderItem, useValue: sliderItem }\n      ]\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(GalleryImageComponent);\n    component = fixture.componentInstance;\n    sanitizer = TestBed.inject(DomSanitizer);\n    fixture.detectChanges();\n  });\n\n  it('should create component', () => {\n    expect(component).toBeTruthy();\n  });\n\n  it('should apply imageState attribute correctly', () => {\n    const nativeElement: HTMLElement = fixture.debugElement.nativeElement;\n\n    component.state.set('loading');\n    fixture.detectChanges();\n    expect(nativeElement.getAttribute('imageState')).toBe('loading');\n\n    component.state.set('success');\n    fixture.detectChanges();\n    expect(nativeElement.getAttribute('imageState')).toBe('success');\n\n    component.state.set('failed');\n    fixture.detectChanges();\n    expect(nativeElement.getAttribute('imageState')).toBe('failed');\n  });\n\n  it('should set image source correctly', () => {\n    fixture.componentRef.setInput('src', 'test-image.jpg');\n    fixture.detectChanges();\n    const img = fixture.debugElement.query(By.css('img'));\n    expect(img.nativeElement.src).toContain('test-image.jpg');\n  });\n\n  it('should apply loading attribute correctly', () => {\n    fixture.componentRef.setInput('loadingAttr', 'lazy');\n    fixture.detectChanges();\n    const img = fixture.debugElement.query(By.css('img'));\n    expect(img.nativeElement.getAttribute('loading')).toBe('lazy');\n  });\n\n  it('should update state to success on image load', () => {\n    const img = fixture.debugElement.query(By.css('img'));\n    img.triggerEventHandler('load', {});\n    fixture.detectChanges();\n    expect(component.state()).toBe('success');\n  });\n\n  it('should update state to failed on image error', () => {\n    spyOn(component.error, 'emit');\n    const img = fixture.debugElement.query(By.css('img'));\n    img.triggerEventHandler('error', new ErrorEvent('error'));\n    fixture.detectChanges();\n    expect(component.state()).toBe('failed');\n    expect(component.error.emit).toHaveBeenCalled();\n  });\n\n  it('should apply visibility correctly based on state when image is thumbnail', () => {\n    fixture.componentRef.setInput('isThumbnail', true);\n    component.state.set('loading');\n    fixture.detectChanges();\n    let img = fixture.debugElement.query(By.css('img'));\n    expect(img.nativeElement.style.visibility).toBe('hidden');\n\n    component.state.set('success');\n    fixture.detectChanges();\n    img = fixture.debugElement.query(By.css('img'));\n    expect(img.nativeElement.style.visibility).toBe('visible');\n  });\n\n  it('should sanitize and set custom loader template', () => {\n    spyOn(sanitizer, 'bypassSecurityTrustHtml').and.callThrough();\n    fixture.componentRef.setInput('loadingIcon', '<div class=\"custom-loader\"></div>');\n    fixture.detectChanges();\n    expect(sanitizer.bypassSecurityTrustHtml).toHaveBeenCalledWith('<div class=\"custom-loader\"></div>');\n  });\n\n  it('should sanitize and set custom error loading template', () => {\n    component.state.set('failed');\n    spyOn(sanitizer, 'bypassSecurityTrustHtml').and.callThrough();\n    fixture.componentRef.setInput('loadingError', '<div class=\"custom-error\"></div>');\n    fixture.detectChanges();\n    expect(sanitizer.bypassSecurityTrustHtml).toHaveBeenCalledWith('<div class=\"custom-error\"></div>');\n    expect(component.errorTemplate()).toEqual(sanitizer.bypassSecurityTrustHtml('<div class=\"custom-error\"></div>'));\n  });\n\n it('should sanitize and set custom error icon template', () => {\n    component.state.set('failed');\n    spyOn(sanitizer, 'bypassSecurityTrustHtml').and.callThrough();\n    fixture.componentRef.setInput('errorIcon', '<div class=\"svg-mock\"></div>');\n    fixture.detectChanges();\n    expect(component.errorSvg()).toEqual(sanitizer.bypassSecurityTrustHtml('<div class=\"svg-mock\"></div>'));\n  });\n});\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/templates/gallery-image.component.ts",
    "content": "import {\n  Component,\n  inject,\n  signal,\n  output,\n  computed,\n  input,\n  Signal,\n  InputSignal,\n  WritableSignal,\n  OutputEmitterRef,\n  ChangeDetectionStrategy\n} from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { animate, style, transition, trigger } from '@angular/animations';\nimport { imageFailedSvg } from './svg-assets';\nimport { ImgRecognizer } from '../utils/img-recognizer';\nimport { ItemState } from '../models/item.model';\n\n@Component({\n  selector: 'gallery-image',\n  host: {\n    '[attr.imageState]': 'state()'\n  },\n  animations: [\n    trigger('fadeIn', [\n      transition('* => success', [\n        style({ opacity: 0 }),\n        animate('300ms ease-in', style({ opacity: 1 }))\n      ])\n    ])\n  ],\n  template: `\n    @if (isThumbnail()) {\n      <img [@fadeIn]=\"state()\"\n           [src]=\"src()\"\n           fill\n           [attr.alt]=\"alt()\"\n           [attr.loading]=\"loadingAttr()\"\n           [style.visibility]=\"state() === 'success' ? 'visible' : 'hidden'\"\n           class=\"g-image-item\"\n           (load)=\"state.set('success')\"\n           (error)=\"state.set('failed'); error.emit($event)\"/>\n    } @else {\n      <img galleryImage\n           [@fadeIn]=\"state()\"\n           [src]=\"src()\"\n           fill\n           [attr.alt]=\"alt()\"\n           [attr.loading]=\"loadingAttr()\"\n           (load)=\"state.set('success')\"\n           (error)=\"state.set('failed'); error.emit($event)\"/>\n    }\n    @switch (state()) {\n      @case ('failed') {\n        <div class=\"g-image-error-message\">\n          @if (errorTemplate()) {\n            <div [innerHTML]=\"errorTemplate()\"></div>\n          } @else {\n            @if (isThumbnail()) {\n              <h4>\n                <div class=\"gallery-thumb-error\" [innerHTML]=\"errorSvg()\"></div>\n              </h4>\n            } @else {\n              <h2>\n                <div class=\"gallery-image-error\" [innerHTML]=\"errorSvg()\"></div>\n              </h2>\n              <p>Unable to load the image!</p>\n            }\n          }\n        </div>\n      }\n      @case ('loading') {\n        @if (loaderTemplate()) {\n          <div class=\"g-loading\" [innerHTML]=\"loaderTemplate()\"></div>\n        } @else if (isThumbnail()) {\n          <div class=\"g-thumb-loading\"></div>\n        }\n      }\n    }\n  `,\n  styleUrl: './gallery-image.scss',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  imports: [ImgRecognizer]\n})\n\nexport class GalleryImageComponent {\n\n  private _sanitizer: DomSanitizer = inject(DomSanitizer);\n\n  state: WritableSignal<ItemState> =  signal<ItemState>('loading');\n\n  /** Is thumbnail */\n  isThumbnail: InputSignal<boolean> = input<boolean>()\n\n  index: InputSignal<number> = input<number>()\n\n  /** Image loading attribute */\n  loadingAttr: InputSignal<'eager' | 'lazy'> = input<'eager' | 'lazy'>();\n\n  /** Image alt */\n  alt: InputSignal<string> = input<string>()\n\n  /** Image source URL */\n  src: InputSignal<string> = input<string>()\n\n  /** Custom loader template */\n  loadingIcon: InputSignal<string> = input<string>();\n\n  /** Custom error template */\n  loadingError: InputSignal<string> = input<string>();\n\n  /** Custom svg template */\n  errorIcon: InputSignal<string> = input<string>(imageFailedSvg);\n\n  /** Custom loader safe template */\n  loaderTemplate: Signal<SafeHtml> = computed(() => this._sanitizer.bypassSecurityTrustHtml(this.loadingIcon()));\n\n  /** Custom error safe template */\n  errorTemplate: Signal<SafeHtml> = computed(() => this._sanitizer.bypassSecurityTrustHtml(this.loadingError()));\n\n  /** Custom svg safe template */\n  errorSvg: Signal<SafeHtml> = computed(() => this._sanitizer.bypassSecurityTrustHtml(this.errorIcon()));\n\n  /** Stream that emits when an error occurs */\n  error: OutputEmitterRef<ErrorEvent> = output<ErrorEvent>();\n\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/templates/gallery-image.scss",
    "content": ":host {\n  position: relative;\n  display: flex;\n  width: 100%;\n  height: 100%;\n  max-height: 100%;\n  max-width: 100%;\n  transition: opacity 300ms cubic-bezier(0.5, 0, 0.5, 1);\n  opacity: var(--g-thumb-opacity);\n\n  &[imageState='success'] {\n    align-self: center;\n  }\n\n  ::ng-deep {\n    svg {\n      width: 100%;\n      height: 100%;\n    }\n  }\n}\n\n.gallery-image-error {\n  width: 100px;\n  height: 100px;\n}\n\n.gallery-thumb-error {\n  width: 40px;\n  height: 40px;\n}\n\nimg.g-image-item {\n  object-fit: var(--image-object-fit);\n  width: 100%;\n  height: 100%;\n  pointer-events: none;\n  max-height: 100%;\n  max-width: 100%;\n}\n\n.g-image-error-message {\n  position: absolute;\n  z-index: 10;\n  left: 0;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  color: white;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  flex-direction: column;\n}\n\nh2, h4 {\n  color: coral;\n  margin: 0;\n}\n\nh2 {\n  font-size: 3.5em;\n  margin-bottom: 0.3em;\n}\n\nh4 {\n  font-size: 1.6em;\n}\n\n.g-loading {\n  position: absolute;\n  transform: translate3d(-50%, -50%, 0);\n  left: 50%;\n  top: 50%;\n  width: 80px;\n  height: 80px;\n}\n\n// Thumbnail loading\n\n$loading-color: #fff !default;\n$placeholder-ng: #262626 !default;\n\n.g-active-thumb {\n  .g-thumb-loading {\n    background-color: #464646;\n  }\n}\n\n.g-thumb-loading {\n  position: relative;\n  overflow: hidden;\n  width: 100%;\n  height: 100%;\n  background-color: $placeholder-ng;\n\n  &::before {\n    content: \"\";\n    position: absolute;\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 50%;\n    z-index: 1;\n    width: 500%;\n    margin-left: -250%;\n    animation: phAnimation .8s linear infinite;\n    background: linear-gradient(to right, rgba($loading-color, 0) 46%, rgba($loading-color, .35) 50%, rgba($loading-color, 0) 54%) 50% 50%;\n  }\n}\n\n@keyframes phAnimation {\n  0% {\n    transform: translate3d(-30%, 0, 0);\n  }\n  100% {\n    transform: translate3d(30%, 0, 0);\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/templates/gallery-video.component.ts",
    "content": "// import {\n//   Component,\n//   output,\n//   effect,\n//   computed,\n//   untracked,\n//   input,\n//   viewChild,\n//   Signal,\n//   ElementRef,\n//   InputSignal,\n//   OutputEmitterRef,\n//   ChangeDetectionStrategy\n// } from '@angular/core';\n//\n// type VideoSources = { url: string, type?: string }[];\n// type VideoSource = string | VideoSources;\n// type VideoControlList = 'nodownload' | 'nofullscreen' | 'noremoteplayback';\n//\n// @Component({\n//   selector: 'gallery-video',\n//   changeDetection: ChangeDetectionStrategy.OnPush,\n//   template: `\n//     <video #video\n//            [attr.mute]=\"mute()\"\n//            [attr.controlsList]=\"controlsList()\"\n//            [attr.disablePictureInPicture]=\"disablePictureInPicture()\"\n//            [disableRemotePlayback]=\"disableRemotePlayback()\"\n//            [controls]=\"controls()\"\n//            [loop]=\"loop()\"\n//            [poster]=\"poster()\"\n//            (error)=\"error.emit($event)\">\n//       @for (src of videoSources(); track src.url) {\n//         @if (src?.type) {\n//           <source [src]=\"src?.url\" [type]=\"src.type\"/>\n//         } @else {\n//           <source [src]=\"src?.url\"/>\n//         }\n//       }\n//     </video>\n//   `\n// })\n// export class GalleryVideoComponent {\n//\n//   video: Signal<ElementRef<HTMLVideoElement>> = viewChild<ElementRef<HTMLVideoElement>>('video');\n//\n//   src: InputSignal<VideoSource> = input<VideoSource>();\n//   poster: InputSignal<string> = input<string>();\n//   mute: InputSignal<boolean> = input<boolean>();\n//   loop: InputSignal<boolean> = input<boolean>();\n//   pause: InputSignal<boolean> = input<boolean>();\n//   autoplay: InputSignal<boolean> = input<boolean>();\n//   controls: InputSignal<boolean> = input<boolean>();\n//   controlsList: InputSignal<VideoControlList> = input<VideoControlList>();\n//   disableRemotePlayback: InputSignal<boolean> = input<boolean>();\n//   disablePictureInPicture: InputSignal<boolean> = input<boolean>();\n//\n//   videoSources: Signal<VideoSources> = computed(() => {\n//     if (this.src instanceof Array) {\n//       // If video has multiple sources\n//       return [...this.src];\n//     }\n//     return [{ url: this.src }];\n//   });\n//\n//   /** Stream that emits when an error occurs */\n//   error: OutputEmitterRef<ErrorEvent> = output<ErrorEvent>();\n//\n//   constructor() {\n//     effect(() => {\n//       const video: HTMLVideoElement = this.video().nativeElement;\n//       const autoplay: boolean = this.autoplay();\n//       const pause: boolean = this.pause();\n//\n//       if (video) {\n//         untracked(() => {\n//           if (autoplay) {\n//             video.play();\n//           }\n//           if (pause) {\n//             video.pause();\n//           }\n//         });\n//       }\n//     });\n//   }\n// }\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/templates/items.model.ts",
    "content": "type GalleryItemModel = {\n  // type?: GalleryItemType;\n  src?: string | { url: string, type: string }[];\n  thumb?: string;\n  args?: any;\n};\n\nexport type ImageItemData = GalleryItemModel & {\n  alt?: string;\n};\n\nexport type IframeItemData = GalleryItemModel & {\n  params?: any;\n};\n\nexport type YoutubeItemData = IframeItemData & {\n  autoplay?: boolean;\n};\n\nexport type VimeoItemData = IframeItemData & {\n  autoplay?: boolean;\n};\n\nexport type VideoItemData = GalleryItemModel & {\n  poster?: string;\n  loop?: boolean;\n  // The only option for boolean video attributes is 'true', because false will still be evaluated to true in attributes binding\n  mute?: true;\n  disablePictureInPicture?: true;\n  controls?: boolean;\n  autoplay?: boolean;\n  preload?: 'none' | 'metadata' | 'auto' | '';\n  controlsList?: 'nodownload' | 'nofullscreen' | 'noremoteplayback';\n  disableRemotePlayback?: boolean;\n};\n\nexport type GalleryItemData = GalleryItemModel;\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/templates/svg-assets.ts",
    "content": "export const imageFailedSvg = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<svg width=\"100\" height=\"100\" version=\"1.1\" viewBox=\"0 0 256 256\" xml:space=\"preserve\" xmlns=\"http://www.w3.org/2000/svg\">\n <g transform=\"translate(1.4066 1.4066) scale(2.81)\">\n\\t<path d=\"m74.453 48.627c-5.538 0-11.075-2.107-15.291-6.324-6.11-6.11-7.768-14.99-5.024-22.629h-48.08c-3.346 1e-3 -6.058 2.713-6.058 6.059v16.322l23.834 20.315c2.278 1.942 5.573 2.119 8.047 0.434l14.382-9.801c2.33-1.588 5.408-1.531 7.677 0.141l27.15 20.001v-25.574c-2.156 0.692-4.394 1.056-6.637 1.056z\" fill=\"#c1e5f4\" stroke-linecap=\"round\"/>\n <circle cx=\"27.942\" cy=\"37.942\" r=\"6.072\" fill=\"#fff0a9\"/>\n <path d=\"m85.446 16.02c-6.061-6.061-15.922-6.061-21.983 0s-6.061 15.923 0 21.984c3.031 3.031 7.011 4.546 10.992 4.546 3.98 0 7.962-1.515 10.992-4.545 2.936-2.937 4.553-6.841 4.553-10.993s-1.617-8.056-4.554-10.992zm-3.555 3.555c1.987 1.986 3.081 4.627 3.081 7.436 0 1.95-0.538 3.813-1.525 5.438l-14.428-14.428c4.043-2.442 9.384-1.934 12.872 1.554zm-14.873 14.874c-3.486-3.487-3.997-8.829-1.554-12.873l14.426 14.427c-4.043 2.443-9.385 1.932-12.872-1.554z\" fill=\"#e29393\" stroke-linecap=\"round\"/>\n <path d=\"m0 40.043v32.425c0 3.346 2.712 6.058 6.058 6.058h68.974c3.346 0 6.058-2.712 6.058-6.058v-1.335l-27.15-20.001c-2.27-1.672-5.348-1.729-7.677-0.141l-14.383 9.801c-2.473 1.686-5.769 1.508-8.047-0.434l-23.833-20.315z\" fill=\"#96ea9c\" stroke-linecap=\"round\"/>\n</g>\n</svg>\n`;\n\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/tests/common.ts",
    "content": "import { Component, Signal, viewChild } from '@angular/core';\nimport { GalleryComponent, GalleryItemData, GalleryItemDef, ImgRecognizer } from 'ng-gallery';\nimport { Observable } from 'rxjs';\nimport { TestBed } from '@angular/core/testing';\nimport { toObservable } from '@angular/core/rxjs-interop';\nimport { img1, img2, img3 } from './test-images';\n\n@Component({\n  imports: [GalleryComponent, GalleryItemDef, ImgRecognizer],\n  template: `\n    <gallery [items]=\"items\" [style.width.px]=\"width\" [style.height.px]=\"height\">\n      <img *galleryItemDef=\"let item\"\n           galleryImage\n           [src]=\"item.src\"/>\n    </gallery>\n  `\n})\nexport class TestComponent {\n  items: GalleryItemData[] = [\n    {\n      // src: 'https://loremflickr.com/200/200?random=1',\n      src: img1\n    },\n    {\n      src: img2\n      // src: 'https://loremflickr.com/200/200?random=2',\n    },\n    {\n      src: img3\n      // src: 'https://loremflickr.com/200/200?random=3',\n    }\n  ];\n  width: number = 500;\n  height: number = 300;\n\n  gallery: Signal<GalleryComponent> = viewChild(GalleryComponent);\n}\n\nexport async function afterTimeout(timeout: number): Promise<void> {\n  // Use await with a setTimeout promise\n  await new Promise<void>((resolve) => setTimeout(resolve, timeout));\n}\n\nexport function getObservableFromContext<T = unknown>(signal: Signal<T>): Observable<T> {\n  let obs;\n  TestBed.runInInjectionContext(() => {\n    obs = toObservable(signal);\n  });\n  return obs;\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/tests/test-images.ts",
    "content": "export const img1 = `data:image/jpeg;base64,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`;\nexport const img2 = `data:image/jpeg;base64,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`;\nexport const img3 = `data:image/jpeg;base64,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`;\n\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/thumbs/gallery-thumbs.component.ts",
    "content": "import {\n  Component,\n  inject,\n  computed,\n  numberAttribute,\n  booleanAttribute,\n  input,\n  contentChild,\n  Signal,\n  InputSignal,\n  TemplateRef,\n  ChangeDetectionStrategy,\n  InputSignalWithTransform\n} from '@angular/core';\nimport { SmoothScroll } from '../smooth-scroll';\nimport { ORIENTATION } from '../models/constants';\nimport { GalleryRef } from '../services/gallery-ref';\nimport { ResizeSensor } from '../services/resize-sensor';\nimport { SliderComponent } from '../slider/slider/slider';\nimport { ScrollSnapType } from '../services/scroll-snap-type';\nimport { HammerSliding } from '../gestures/hammer-sliding.directive';\nimport { SliderItem } from '../slider/slider-item/slider-item';\nimport { GalleryItemContext, GalleryItemDef } from '../directives/gallery-item-def.directive';\nimport { GalleryItemData } from '../templates/items.model';\nimport { ImageSize, ThumbsPosition } from '../models/config.model';\n// import { IntersectionSensor } from '../observers/intersection-sensor.directive';\n\n@Component({\n  host: {\n    '[attr.autosize]': 'autosize()',\n    '[attr.disabled]': 'disabled()',\n    '[attr.scrollDisabled]': 'disableScroll()',\n    '[attr.imageSize]': 'imageSize()',\n    '[attr.position]': 'position()',\n    '[style.grid-area]': 'position()',\n    '[style.--g-thumb-width.px]': 'thumbWidth()',\n    '[style.--g-thumb-height.px]': 'thumbHeight()'\n  },\n  selector: 'gallery-thumbs',\n  template: `\n    <g-slider [orientation]=\"orientation()\"\n              [autosize]=\"autosize()\"\n              [centralized]=\"centralized()\"\n              isThumbs\n              resizeSensor\n              [smoothScroll]=\"detach()\"\n              hammerSliding\n              scrollSnapType>\n      <div class=\"g-slider-content\">\n        @for (item of galleryRef.items(); track i; let i = $index; let count = $count) {\n          <slider-item [data]=\"item\"\n                       [template]=\"template()\"\n                       [currIndex]=\"galleryRef.currIndex()\"\n                       [index]=\"i\"\n                       [count]=\"count\"\n                       (click)=\"disabled() || galleryRef.set(i)\"/>\n        }\n      </div>\n    </g-slider>\n    <ng-content/>\n  `,\n  styleUrl: './gallery-thumbs.scss',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  imports: [\n    // IntersectionSensor,\n    SmoothScroll,\n    HammerSliding,\n    ResizeSensor,\n    ScrollSnapType,\n    SliderComponent,\n    SliderItem\n  ]\n})\nexport class GalleryThumbsComponent {\n\n  readonly galleryRef: GalleryRef = inject(GalleryRef);\n\n  /**\n   * Fits each thumbnail size to its content\n   */\n  autosize: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(false, {\n    transform: booleanAttribute\n  });\n\n  /**\n   * Centralize active thumb\n   */\n  centralized: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(false, {\n    transform: booleanAttribute\n  });\n\n  /**\n   * Disables thumbnails' clicks\n   */\n  disabled: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(false, {\n    transform: booleanAttribute\n  });\n\n  /**\n   * Disables sliding of thumbnails using touchpad, scroll and gestures on touch devices\n   */\n  disableScroll: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(false, {\n    transform: booleanAttribute\n  });\n\n  /**\n   * Disables sliding of thumbnails using the mouse\n   */\n  disableMouseScroll: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(false, {\n    transform: booleanAttribute\n  });\n\n  /**\n   * De-attaching the thumbnails from the main slider\n   * If enabled - thumbnails won't automatically scroll to the active thumbnails\n   */\n  detach: InputSignalWithTransform<boolean, string | boolean> = input<boolean, string | boolean>(false, {\n    transform: booleanAttribute\n  });\n\n  /**\n   * TODO: Rename this to align and add start and end options to work with RTL\n   * Sets the thumbnails position, it also sets the sliding direction of the thumbnails accordingly\n   */\n  position: InputSignal<ThumbsPosition> = input<ThumbsPosition>('bottom');\n\n  /**\n   * Sets the object-fit style applied on items' images\n   */\n  imageSize: InputSignal<ImageSize> = input<ImageSize>('cover');\n\n  /**\n   * Sets the thumbnail's width\n   */\n  thumbWidth: InputSignalWithTransform<number, string | number> = input<number, string | number>(120, {\n    transform: numberAttribute\n  });\n\n  /**\n   * Sets the thumbnail's height\n   */\n  thumbHeight: InputSignalWithTransform<number, string | number> = input<number, string | number>(90, {\n    transform: numberAttribute\n  });\n\n  orientation: Signal<ORIENTATION> = computed(() => {\n    return (this.position() === 'top' || this.position() === 'bottom') ? ORIENTATION.Horizontal : ORIENTATION.Vertical;\n  });\n\n  /** @ignore */\n  private itemDef: Signal<GalleryItemDef> = contentChild(GalleryItemDef);\n\n  template: Signal<TemplateRef<GalleryItemContext<GalleryItemData>>> = computed(() => this.itemDef()?.templateRef)\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/thumbs/gallery-thumbs.scss",
    "content": ":host {\n  max-height: 100%;\n  max-width: 100%;\n  display: block;\n  z-index: 100;\n\n  // Forward thumb variables\n  --g-item-cursor: var(--g-thumb-cursor);\n  --g-item-height: var(--g-thumb-height);\n  --g-item-width: var(--g-thumb-width);\n\n  &[position=\"top\"],\n  &[position=\"bottom\"] {\n    --thumb-slider-top: 0;\n    --thumb-slider-overflow: auto hidden;\n    --thumb-slider-flex-direction: row;\n    --g-thumb-height: 100%;\n\n    &[autosize=\"true\"] {\n      --g-item-width: auto !important;\n    }\n  }\n\n  &[position=\"left\"],\n  &[position=\"right\"] {\n    --thumb-slider-left: 0;\n    --thumb-slider-overflow: hidden auto;\n    --thumb-slider-flex-direction: column;\n    --g-thumb-width: 100%;\n\n    &[autosize=\"true\"] {\n      --g-thumb-height: auto !important;\n    }\n  }\n\n  &[disabled=\"true\"] {\n    --g-thumb-cursor: default;\n  }\n\n  &[scrollDisabled=\"true\"] {\n    --thumb-slider-overflow: hidden !important;\n  }\n\n  &[imageSize=\"contain\"] {\n    --image-object-fit: contain;\n  }\n\n  &[imageSize=\"cover\"] {\n    --image-object-fit: cover;\n  }\n}\n\n.g-slider {\n  display: flex;\n  align-items: center;\n  transition: var(--g-height-transition);\n  max-height: 100%;\n  min-width: 100%;\n  height: var(--thumb-slider-height);\n  width: var(--thumb-slider-width);\n\n  top: var(--thumb-slider-top);\n  left: var(--thumb-slider-left);\n  overflow: var(--thumb-slider-overflow);\n  scroll-snap-type: var(--slider-scroll-snap-type);\n  flex-direction: var(--thumb-slider-flex-direction);\n\n  scrollbar-width: none;\n\n  &::-webkit-scrollbar {\n    display: none;\n  }\n\n  &.g-sliding {\n    // Disable mouse click on gallery items/thumbnails when the slider is being dragged using the mouse\n    .g-slider-content {\n      pointer-events: none;\n    }\n  }\n\n  &[centralised=\"true\"] {\n    &:before, &:after {\n      content: \"\";\n    }\n\n    &:before {\n      flex: 0 0 var(--centralize-start-size);\n    }\n\n    &:after {\n      flex: 0 0 var(--centralize-end-size);\n    }\n  }\n}\n\n.g-slider-content {\n  // Never set min-width to 100%, content wrapper should always match content size to measure centralize size\n  min-width: 100%;\n  flex: 0 0 auto;\n  display: flex;\n  flex-direction: var(--thumb-slider-flex-direction);\n  align-items: center;\n  //gap: 1px;\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/thumbs/gallery-thumbs.spec.ts",
    "content": "import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing';\nimport { NoopAnimationsModule } from '@angular/platform-browser/animations';\nimport { Component, DebugElement, Signal, viewChild } from '@angular/core';\nimport { By } from '@angular/platform-browser';\nimport {\n  GalleryThumbsComponent,\n  GalleryComponent,\n  GalleryItemDef,\n  GalleryRef,\n  ImgRecognizer,\n  GalleryItemData\n} from 'ng-gallery';\nimport { img1, img2, img3 } from '../tests/test-images';\n\n@Component({\n  imports: [GalleryComponent, GalleryThumbsComponent, GalleryItemDef, ImgRecognizer],\n  template: `\n    <gallery [items]=\"items\" [style.width.px]=\"width\" [style.height.px]=\"height\">\n      <img *galleryItemDef=\"let item\"\n           galleryImage\n           [src]=\"item.src\"/>\n\n      <gallery-thumbs [position]=\"position\"\n                      [centralized]=\"centralized\"\n                      [imageSize]=\"imageSize\"\n                      [autosize]=\"autosize\"\n                      [thumbWidth]=\"thumbWidth\"\n                      [thumbHeight]=\"thumbHeight\"\n                      [disabled]=\"disabled\"\n                      [disableScroll]=\"scrollDisabled\"/>\n    </gallery>\n  `\n})\nexport class TestComponent {\n  items: GalleryItemData[] = [\n    { src: img1, thumb: img1 },\n    { src: img2, thumb: img2 },\n    { src: img3, thumb: img3 }\n  ];\n  width: number = 500;\n  height: number = 300;\n\n  scrollBehavior: ScrollBehavior = 'smooth';\n  position: 'top' | 'left' | 'right' | 'bottom' = 'bottom';\n  imageSize: 'cover' | 'contain' = 'cover';\n  centralized: boolean = false;\n  autosize: boolean = false;\n  disabled: boolean = false;\n  scrollDisabled: boolean = false;\n  thumbWidth: number = 60;\n  thumbHeight: number = 60;\n\n  gallery: Signal<GalleryComponent> = viewChild(GalleryComponent);\n}\n\ndescribe('Gallery thumbs component', () => {\n  let fixture: ComponentFixture<TestComponent>;\n  let component: TestComponent;\n  let thumbsComponent: GalleryThumbsComponent;\n  let galleryRef: GalleryRef;\n  let thumbsComponentElement: DebugElement;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [\n        NoopAnimationsModule,\n        TestComponent\n      ],\n      providers: [\n        { provide: ComponentFixtureAutoDetect, useValue: true }\n      ]\n    }).compileComponents();\n\n    fixture = TestBed.createComponent(TestComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n\n    thumbsComponentElement = fixture.debugElement.query(By.directive(GalleryThumbsComponent));\n    thumbsComponent = thumbsComponentElement.injector.get(GalleryThumbsComponent);\n    galleryRef = thumbsComponentElement.injector.get(GalleryRef);\n  });\n\n  it('should create gallery-thumbs component', () => {\n    expect(thumbsComponent).toBeTruthy();\n    expect(galleryRef).toBeTruthy();\n  });\n\n  it('should set the orientation', () => {\n    component.position = 'bottom';\n    fixture.detectChanges();\n    expect(thumbsComponent.orientation()).toBe('horizontal');\n    expect((thumbsComponentElement.nativeElement as HTMLElement).style.getPropertyValue('grid-area')).toBe('bottom');\n\n    component.position = 'left';\n    fixture.detectChanges();\n    expect(thumbsComponent.orientation()).toBe('vertical');\n    expect((thumbsComponentElement.nativeElement as HTMLElement).style.getPropertyValue('grid-area')).toBe('left');\n\n    component.position = 'top';\n    fixture.detectChanges();\n    expect(thumbsComponent.orientation()).toBe('horizontal');\n    expect((thumbsComponentElement.nativeElement as HTMLElement).style.getPropertyValue('grid-area')).toBe('top');\n\n    component.position = 'right';\n    fixture.detectChanges();\n    expect(thumbsComponent.orientation()).toBe('vertical');\n    expect((thumbsComponentElement.nativeElement as HTMLElement).style.getPropertyValue('grid-area')).toBe('right');\n  });\n\n  it('should set the imageSize attribute', () => {\n    component.imageSize = 'cover';\n    fixture.detectChanges();\n    expect((thumbsComponentElement.nativeElement as HTMLElement).getAttribute('imageSize')).toBe('cover');\n\n    component.imageSize = 'contain';\n    fixture.detectChanges();\n    expect((thumbsComponentElement.nativeElement as HTMLElement).getAttribute('imageSize')).toBe('contain');\n  });\n\n  it('should set the autosize attribute', () => {\n    component.autosize = false;\n    fixture.detectChanges();\n    expect((thumbsComponentElement.nativeElement as HTMLElement).getAttribute('autosize')).toBe('false');\n\n    component.autosize = true;\n    fixture.detectChanges();\n    expect((thumbsComponentElement.nativeElement as HTMLElement).getAttribute('autosize')).toBe('true');\n  });\n\n  it('should set the disabled attribute', () => {\n    component.disabled = false;\n    fixture.detectChanges();\n    expect((thumbsComponentElement.nativeElement as HTMLElement).getAttribute('disabled')).toBe('false');\n\n    component.disabled = true;\n    fixture.detectChanges();\n    expect((thumbsComponentElement.nativeElement as HTMLElement).getAttribute('disabled')).toBe('true');\n  });\n\n  it('should set the scrollDisabled attribute', () => {\n    component.scrollDisabled = false;\n    fixture.detectChanges();\n    expect((thumbsComponentElement.nativeElement as HTMLElement).getAttribute('scrollDisabled')).toBe('false');\n\n    component.scrollDisabled = true;\n    fixture.detectChanges();\n    expect((thumbsComponentElement.nativeElement as HTMLElement).getAttribute('scrollDisabled')).toBe('true');\n  });\n\n  it('should set the thumbWidth and thumbHeight CSS variable', () => {\n    component.thumbWidth = 100;\n    component.thumbHeight = 100;\n    fixture.detectChanges();\n    expect((thumbsComponentElement.nativeElement as HTMLElement).style.getPropertyValue('--g-thumb-width')).toBe('100px');\n    expect((thumbsComponentElement.nativeElement as HTMLElement).style.getPropertyValue('--g-thumb-height')).toBe('100px');\n  });\n});\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/utils/gallery.default.ts",
    "content": "import {\n  IMAGE_SIZE,\n  ORIENTATION,\n} from '../models/constants';\nimport { GalleryConfig } from '../models/config.model';\n\nexport const defaultConfig: GalleryConfig = {\n  loop: false,\n  debug: false,\n  autoplay: false,\n  disableScroll: false,\n  disableMouseScroll: false,\n  autoplayInterval: 3000,\n  scrollDuration: 468,\n  scrollEase: {\n    x1: 0.42,\n    y1: 0,\n    x2: 0.58,\n    y2: 1\n  },\n  centralized: false,\n  itemAutosize: false,\n  scrollBehavior: 'smooth',\n  resizeDebounceTime: 0,\n  imageSize: IMAGE_SIZE.Contain,\n  orientation: ORIENTATION.Horizontal,\n  navIcon: `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><path d=\"M0 256C0 397.4 114.6 512 256 512s256-114.6 256-256S397.4 0 256 0S0 114.6 0 256zM241 377c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l87-87-87-87c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0L345 239c9.4 9.4 9.4 24.6 0 33.9L241 377z\"/></svg>`,\n  // navIcon: `<?xml version=\"1.0\" encoding=\"UTF-8\"?><svg width=\"512px\" height=\"512px\" enable-background=\"new 0 0 240.823 240.823\" version=\"1.1\" viewBox=\"0 0 240.823 240.823\" xml:space=\"preserve\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"m183.19 111.82l-108.3-108.26c-4.752-4.74-12.451-4.74-17.215 0-4.752 4.74-4.752 12.439 0 17.179l99.707 99.671-99.695 99.671c-4.752 4.74-4.752 12.439 0 17.191 4.752 4.74 12.463 4.74 17.215 0l108.3-108.26c4.68-4.691 4.68-12.511-0.012-17.19z\"></svg>`,\n  loadingIcon: `<?xml version=\"1.0\" encoding=\"UTF-8\"?><svg stroke=\"#fff\" viewBox=\"0 0 44 44\" xmlns=\"http://www.w3.org/2000/svg\"><g fill=\"none\" fill-rule=\"evenodd\" stroke-width=\"2\"><circle cx=\"22\" cy=\"22\" r=\"1\"><animate attributeName=\"r\" begin=\"0s\" calcMode=\"spline\" dur=\"1.8s\" keySplines=\"0.165, 0.84, 0.44, 1\" keyTimes=\"0; 1\" repeatCount=\"indefinite\" values=\"1; 20\"/><animate attributeName=\"stroke-opacity\" begin=\"0s\" calcMode=\"spline\" dur=\"1.8s\" keySplines=\"0.3, 0.61, 0.355, 1\" keyTimes=\"0; 1\" repeatCount=\"indefinite\" values=\"1; 0\"/></circle><circle cx=\"22\" cy=\"22\" r=\"1\"><animate attributeName=\"r\" begin=\"-0.9s\" calcMode=\"spline\" dur=\"1.8s\" keySplines=\"0.165, 0.84, 0.44, 1\" keyTimes=\"0; 1\" repeatCount=\"indefinite\" values=\"1; 20\"/><animate attributeName=\"stroke-opacity\" begin=\"-0.9s\" calcMode=\"spline\" dur=\"1.8s\" keySplines=\"0.3, 0.61, 0.355, 1\" keyTimes=\"0; 1\" repeatCount=\"indefinite\" values=\"1; 0\"/></circle></g></svg>`\n};\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/utils/img-manager.spec.ts",
    "content": "import { TestBed } from '@angular/core/testing';\nimport { BehaviorSubject } from 'rxjs';\nimport { GalleryRef } from '../services/gallery-ref';\nimport { ImgManager } from './img-manager';\nimport { ItemState } from '../models/item.model';\n\ndescribe('ImgManager', () => {\n  let service: ImgManager;\n  let galleryRefMock: jasmine.SpyObj<GalleryRef>;\n  let currIndexSubject: BehaviorSubject<number>;\n\n  beforeEach(() => {\n    currIndexSubject = new BehaviorSubject<number>(0);\n    galleryRefMock = jasmine.createSpyObj('GalleryRef', ['currIndex']);\n    (galleryRefMock['currIndex'] as any) = jasmine.createSpy().and.callFake(() => currIndexSubject.getValue());\n\n    TestBed.configureTestingModule({\n      providers: [\n        ImgManager,\n        { provide: GalleryRef, useValue: galleryRefMock }\n      ]\n    });\n\n    service = TestBed.inject(ImgManager);\n  });\n\n  it('should be created', () => {\n    expect(service).toBeTruthy();\n  });\n\n  it('should add an image and trigger an update', (done: DoneFn) => {\n    const state$ = new BehaviorSubject<ItemState>('success');\n    const imgElement = document.createElement('img');\n    const registry = { state$: state$.asObservable(), target: imgElement };\n\n    service.addItem(0, registry);\n\n    service.getActiveItem().subscribe((img) => {\n      expect(img).toBe(imgElement);\n      done();\n    });\n  });\n\n  it('should delete an image and trigger an update with null', (done: DoneFn) => {\n    const state$ = new BehaviorSubject<ItemState>('success');\n    const imgElement = document.createElement('img');\n    const registry = { state$: state$.asObservable(), target: imgElement };\n\n    service.addItem(0, registry);\n    service.deleteItem(0);\n\n    service.getActiveItem().subscribe((img) => {\n      expect(img).toBeNull(); // Expect null instead of an empty observable\n      done();\n    });\n  });\n\n  it('should return null if no image is registered for current index', (done: DoneFn) => {\n    service.getActiveItem().subscribe((img) => {\n      expect(img).toBeNull(); // Expect null instead of failing\n      done();\n    });\n  });\n\n  it('should switch to new active image when index changes', (done: DoneFn) => {\n    const state1$ = new BehaviorSubject<ItemState>('success');\n    const imgElement1 = document.createElement('img');\n    service.addItem(0, { state$: state1$.asObservable(), target: imgElement1 });\n\n    const state2$ = new BehaviorSubject<ItemState>('success');\n    const imgElement2 = document.createElement('img');\n    service.addItem(1, { state$: state2$.asObservable(), target: imgElement2 });\n\n    currIndexSubject.next(1);\n\n    service.getActiveItem().subscribe((img) => {\n      expect(img).toBe(imgElement2);\n      done();\n    });\n  });\n});\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/utils/img-manager.ts",
    "content": "import { inject, Injectable } from '@angular/core';\nimport { toObservable } from '@angular/core/rxjs-interop';\nimport { Observable, Subject, filter, map, switchMap, merge, of } from 'rxjs';\nimport { GalleryRef } from '../services/gallery-ref';\nimport { ItemState } from '../models/item.model';\n\ninterface ImageRegistry {\n  state$: Observable<ItemState>;\n  target: HTMLImageElement;\n}\n\n/**\n * A service used to notify when the active image is loaded\n * Used for auto-height feature, autoplay feature\n */\n@Injectable()\nexport class ImgManager {\n\n  private readonly galleryRef: GalleryRef = inject(GalleryRef);\n\n  private readonly trigger$: Subject<void> = new Subject<void>();\n\n  private readonly currIndex$: Observable<number> = toObservable(this.galleryRef.currIndex);\n\n  private readonly images: Map<number, ImageRegistry> = new Map<number, ImageRegistry>();\n\n  getActiveItem(): Observable<HTMLImageElement> {\n    return merge(this.currIndex$, this.trigger$).pipe(\n      switchMap(() => {\n        const img: ImageRegistry = this.images.get(this.galleryRef.currIndex());\n        if (img) {\n          return img.state$.pipe(\n            filter((state: ItemState) => state !== 'loading'),\n            map(() => img.target)\n          )\n        }\n        return of(null);\n      })\n    );\n  }\n\n  addItem(index: number, payload: ImageRegistry): void {\n    this.images.set(index, payload);\n    this.trigger$.next();\n  }\n\n  deleteItem(index: number): void {\n    if (this.images.has(index)) {\n      this.images.delete(index);\n      this.trigger$.next();\n    }\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/utils/img-recognizer.spec.ts",
    "content": "import { Component, Signal, viewChild, } from '@angular/core';\nimport { ComponentFixture, TestBed } from '@angular/core/testing';\nimport { GalleryItemDef, GalleryRef, ImgRecognizer } from 'ng-gallery';\nimport { ImgManager } from './img-manager';\nimport { SliderItem } from '../slider/slider-item/slider-item';\n\n@Component({\n  imports: [SliderItem, ImgRecognizer, GalleryItemDef],\n  template: `\n    <slider-item [data]=\"{}\"\n                 [currIndex]=\"0\"\n                 [index]=\"0\"\n                 [count]=\"1\"\n                 [template]=\"itemDef().templateRef\"/>\n\n    <img *galleryItemDef galleryImage>\n\n    @if (testErrorCase) {\n      <img galleryImage>\n    }\n  `\n})\nclass TestComponent {\n  imgRecognizerDirective: Signal<ImgRecognizer> = viewChild(ImgRecognizer);\n  itemDef: Signal<GalleryItemDef> = viewChild(GalleryItemDef);\n  item: Signal<SliderItem> = viewChild(SliderItem);\n\n  testErrorCase: boolean;\n}\n\ndescribe('ImgRecognizer Directive', () => {\n  let fixture: ComponentFixture<TestComponent>;\n  let component: TestComponent;\n  let imgManager: ImgManager;\n\n  beforeEach(() => {\n    TestBed.configureTestingModule({\n      imports: [TestComponent],\n      providers: [ImgManager, GalleryRef]\n    }).compileComponents();\n\n    imgManager = TestBed.inject(ImgManager);\n  });\n\n  it('should create an instance', () => {\n    fixture = TestBed.createComponent(TestComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n\n    expect(component.imgRecognizerDirective()).toBeTruthy();\n  });\n\n  it('should throw an error if used outside a SliderItem', () => {\n    TestBed.resetTestingModule(); // Reset the module to allow provider overrides\n    TestBed.configureTestingModule({\n      imports: [TestComponent],\n      providers: [\n        ImgManager,\n        GalleryRef,\n        {\n          provide: SliderItem, useValue: null\n        }\n      ]\n    });\n    fixture = TestBed.createComponent(TestComponent);\n    fixture.componentInstance.testErrorCase = true;\n    expect(() => fixture.detectChanges()).toThrowError(\n      '[NgGallery]: galleryImage directive should be only used inside gallery item templates!'\n    );\n  });\n\n  it('should set isItemContainImage to true on initialization', () => {\n    fixture = TestBed.createComponent(TestComponent);\n    component = fixture.componentInstance;\n\n    fixture.detectChanges();\n    expect(component.item().isItemContainImage).toBeTrue();\n  });\n\n  it('should register image in ImgManager when index is set', () => {\n    fixture = TestBed.createComponent(TestComponent);\n    component = fixture.componentInstance;\n\n    spyOn(imgManager, 'addItem');\n    fixture.detectChanges();\n\n    expect(imgManager.addItem).toHaveBeenCalledWith(0, {\n      state$: component.item().state$,\n      target: component.imgRecognizerDirective().nativeElement,\n    });\n  });\n\n  it('should unregister image from ImgManager on cleanup', () => {\n    fixture = TestBed.createComponent(TestComponent);\n\n    spyOn(imgManager, 'deleteItem');\n    fixture.detectChanges();\n\n    fixture.destroy();\n    expect(imgManager.deleteItem).toHaveBeenCalledWith(0);\n  });\n\n  it('should update state to \"success\" on image load', () => {\n    fixture = TestBed.createComponent(TestComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n\n    component.imgRecognizerDirective().nativeElement.dispatchEvent(new Event('load'));\n    expect(component.item().state()).toBe('success');\n  });\n\n  it('should update state to \"failed\" on image error', () => {\n    fixture = TestBed.createComponent(TestComponent);\n    component = fixture.componentInstance;\n    fixture.detectChanges();\n\n    component.imgRecognizerDirective().nativeElement.dispatchEvent(new Event('error'));\n    expect(component.item().state()).toBe('failed');\n  });\n});\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/utils/img-recognizer.ts",
    "content": "import {\n  Directive,\n  inject,\n  effect,\n  untracked,\n  ElementRef,\n  EffectCleanupRegisterFn\n} from '@angular/core';\nimport { ImgManager } from './img-manager';\nimport { SliderItem } from '../slider/slider-item/slider-item';\n\n/**\n * A directive used to register an img element in the ImgManager service to track img loading state\n */\n@Directive({\n  selector: 'img[galleryImage]',\n  host: {\n    '[class.g-image-item]': 'true',\n    // '[style.visibility]': 'item.state() === \"success\" ? \"visible\" : \"hidden\"',\n    '(load)': 'item.state.set(\"success\")',\n    '(error)': 'item.state.set(\"failed\")'\n  }\n})\nexport class ImgRecognizer {\n\n  readonly nativeElement: HTMLImageElement = inject(ElementRef<HTMLImageElement>).nativeElement;\n\n  private readonly manager: ImgManager = inject(ImgManager);\n\n  readonly item: SliderItem = inject(SliderItem);\n\n  constructor() {\n    if (this.item) {\n      // Mark the gallery-item component as an image item\n      this.item.isItemContainImage = true;\n    } else {\n      throw new Error('[NgGallery]: galleryImage directive should be only used inside gallery item templates!')\n    }\n\n    effect((onCleanup: EffectCleanupRegisterFn) => {\n      const index: number = this.item.index();\n\n      untracked(() => {\n        if (index != null) {\n          this.manager.addItem(index, {\n            state$: this.item.state$,\n            target: this.nativeElement\n          });\n\n          onCleanup(() => this.manager.deleteItem(index));\n        }\n      });\n    });\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/utils/item.class.spec.ts",
    "content": "import {\n  ITEM_TYPE,\n  IframeItemData,\n  ImageItemData,\n  VideoItemData,\n  VimeoItemData,\n  YoutubeItemData\n} from 'ng-gallery';\nimport { IframeItem, ImageItem, VideoItem, VimeoItem, YoutubeItem } from './item.class';\n\ndescribe('Gallery Item Classes', () => {\n  it('should create an ImageItem with correct type and data', () => {\n    const data: ImageItemData = { src: 'image.jpg' };\n    const item = new ImageItem(data);\n    expect(item.type).toBe(ITEM_TYPE.Image);\n    expect(item.data).toEqual(data);\n  });\n\n  it('should create a VideoItem with correct type and data', () => {\n    const data: VideoItemData = { src: 'video.mp4' };\n    const item = new VideoItem(data);\n    expect(item.type).toBe(ITEM_TYPE.Video);\n    expect(item.data).toEqual(data);\n  });\n\n  it('should create an IframeItem with correct type and data', () => {\n    const data: IframeItemData = { src: 'https://example.com' };\n    const item = new IframeItem(data);\n    expect(item.type).toBe(ITEM_TYPE.Iframe);\n    expect(item.data).toEqual(data);\n  });\n\n  it('should create a YoutubeItem with correct type and transformed data when thumb is not provided', () => {\n    const data: YoutubeItemData = { src: 'abc123' };\n    const item = new YoutubeItem(data);\n    expect(item.type).toBe(ITEM_TYPE.Youtube);\n    expect(item.data.src).toBe('https://youtube.com/embed/abc123');\n    expect(item.data.thumb).toBe('//img.youtube.com/vi/abc123/default.jpg');\n  });\n\n  it('should create a YoutubeItem with correct type and transformed data when thumb is provided', () => {\n    const data: YoutubeItemData = { src: 'abc123', thumb: 'https://example.com/thumb-abc123' };\n    const item = new YoutubeItem(data);\n    expect(item.type).toBe(ITEM_TYPE.Youtube);\n    expect(item.data.src).toBe('https://youtube.com/embed/abc123');\n    expect(item.data.thumb).toBe('https://example.com/thumb-abc123');\n  });\n\n  it('should create a VimeoItem with correct type and transformed data when thumb is not provided', () => {\n    const data: VimeoItemData = { src: '456789' };\n    const item = new VimeoItem(data);\n    expect(item.type).toBe(ITEM_TYPE.Vimeo);\n    expect(item.data.src).toBe('https://player.vimeo.com/video/456789');\n    expect(item.data.thumb).toBe('//vumbnail.com/456789.jpg');\n  });\n\n  it('should create a VimeoItem with correct type and transformed data when thumb is provided', () => {\n    const data: VimeoItemData = { src: '456789', thumb: 'https://example.com/thumb-456789' };\n    const item = new VimeoItem(data);\n    expect(item.type).toBe(ITEM_TYPE.Vimeo);\n    expect(item.data.src).toBe('https://player.vimeo.com/video/456789');\n    expect(item.data.thumb).toBe('https://example.com/thumb-456789');\n  });\n});\n"
  },
  {
    "path": "projects/ng-gallery/src/lib/utils/item.class.ts",
    "content": "import { GalleryItemType, ITEM_TYPE } from '../models/constants';\nimport { IframeItemData, ImageItemData, VideoItemData, VimeoItemData, YoutubeItemData } from '../templates/items.model';\n\ninterface GalleryItem {\n  type: GalleryItemType;\n  data: any;\n}\n\nexport class ImageItem implements GalleryItem {\n  readonly type: GalleryItemType;\n  readonly data: ImageItemData;\n\n  constructor(data: ImageItemData) {\n    this.data = data;\n    this.type = ITEM_TYPE.Image;\n  }\n}\n\nexport class VideoItem implements GalleryItem {\n  readonly type: GalleryItemType;\n  readonly data: VideoItemData;\n\n  constructor(data: VideoItemData) {\n    this.data = data;\n    this.type = ITEM_TYPE.Video;\n  }\n}\n\nexport class IframeItem implements GalleryItem {\n  readonly type: GalleryItemType;\n  readonly data: IframeItemData;\n\n  constructor(data: IframeItemData) {\n    this.data = data;\n    this.type = ITEM_TYPE.Iframe;\n  }\n}\n\nexport class YoutubeItem implements GalleryItem {\n  readonly type: GalleryItemType;\n  readonly data: YoutubeItemData;\n\n  constructor(data: YoutubeItemData) {\n    this.data = {\n      ...data,\n      ...{\n        src: `https://youtube.com/embed/${ data.src }`,\n        thumb: data.thumb ?? `//img.youtube.com/vi/${ data.src }/default.jpg`\n      }\n    };\n    this.type = ITEM_TYPE.Youtube;\n  }\n}\n\nexport class VimeoItem implements GalleryItem {\n  readonly type: GalleryItemType;\n  readonly data: VimeoItemData;\n\n  constructor(data: VimeoItemData) {\n    this.data = {\n      ...data,\n      ...{\n        src: `https://player.vimeo.com/video/${ data.src }`,\n        thumb: data.thumb ?? this.getVimeoThumb(data.src as string)\n      }\n    };\n\n\n    this.type = ITEM_TYPE.Vimeo;\n  }\n\n  private getVimeoThumb(videoId: string): string {\n    //Vimeo has no API for getting a thumbnail, but this project can do it: https://github.com/ThatGuySam/vumbnail\n    return `//vumbnail.com/${ videoId }.jpg`\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/public-api.ts",
    "content": "export * from './lib/services/gallery-ref';\nexport * from './lib/nav/gallery-nav.component';\nexport * from './lib/thumbs/gallery-thumbs.component';\nexport * from './lib/bullets/gallery-bullets.component';\nexport * from './lib/counter/gallery-counter.component';\nexport * from './lib/utils/img-recognizer';\nexport * from './lib/core/gallery.component';\nexport * from './lib/templates/items.model';\nexport * from './lib/templates/gallery-image.component';\nexport * from './lib/models/config.model';\nexport * from './lib/models/constants';\nexport * from './lib/gallery.module';\nexport * from './lib/directives/gallery-item-def.directive';\nexport * from './lib/directives/gallery-box-def.directive';\nexport * from './lib/utils/item.class';\n// export * from './lib/templates/gallery-iframe.component';\n// export * from './lib/templates/gallery-video.component';\n// export * from './lib/autoplay/autoplay.directive';\n// export * from './lib/auto-height/auto-height';\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/.eslintrc.json",
    "content": "{\n  \"rules\": {\n    \"@typescript-eslint/consistent-type-imports\": [\"error\", { \"disallowTypeAnnotations\": false }]\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/GettingStarted.mdx",
    "content": "<div className=\"logo-container\">\n  <img src=\"https://user-images.githubusercontent.com/8130692/36171173-ad0da54c-1112-11e8-85bf-843c5f70efdc.png\"\n       alt=\"Gallery Logo\"/>\n  <h1>Angular Gallery</h1>\n</div>\n\n\n# Getting Started\n\nThis library consists of 2 packages:\n\n- **ng-gallery:** This package includes a `<gallery>` component and a gallery service.\n- **ng-gallery/lightbox:** Within this package, you'll find a lightbox service and a `[lightbox]` directive, designed to facilitate the opening of the gallery within a modal window.\nAdditionally, this package offers a `[gallerize]` directive, which seamlessly integrates the images into the lightbox.\n\n## Installation\n\nThe package can be installed alongside the `@angular/cdk` dependency using NPM. To do this, run the following command in your terminal:\n\n```bash\nnpm i ng-gallery@beta @angular/cdk\n```\n\n## Usage\n\nThe `GalleryModule` can be imported either globally in your application configuration or directly in your component imports.\nIt's important to note that importing the `provideAnimations` animation is required for the proper functioning of the gallery component.\nBelow is an example of setting up your Angular application:\n\n```ts\nimport { ApplicationConfig, importProvidersFrom } from '@angular/core';\nimport { provideAnimations } from '@angular/platform-browser/animations';\nimport { GalleryModule } from 'ng-gallery';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideAnimations(),\n    importProvidersFrom(GalleryModule)\n  ]\n};\n```\n\n### Provide default options\n\n`DEFAULT_GALLERY_CONFIG` Injection token that can be used to provide the default options for the gallery.\n\n**Example**\n\n\n```ts\nimport { ApplicationConfig } from '@angular/core';\nimport { DEFAULT_GALLERY_CONFIG, GalleryConfig } from 'ng-gallery';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    {\n      provide: DEFAULT_GALLERY_CONFIG,\n      useValue: {\n        imageSize: 'cover'\n      } as GalleryConfig\n    }\n  ]\n};\n```\n\n## Mouse Sliding (optional)\n\nThe gallery relies on HammerJS to support dragging the slider using the mouse, if you don't want this feature you can skip installing HammerJS.\n\n*You can add HammerJS to your application via [npm](https://www.npmjs.com/package/hammerjs), a CDN (such as the [Google CDN](https://developers.google.com/speed/libraries/#hammerjs)), or served directly from your app.*\n\nTo install via npm, use the following command:\n\n**NPM**\n\n```bash\nnpm i hammerjs\n```\n\nAfter installing, import it in `main.ts`\n\n```ts\nimport 'hammerjs';\n```\n\n\n## Issues\n\nIf 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).\n\n\n\n<style>\n  {`\n  .logo-container {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n    gap: 24px;\n  }\n  .logo-container img {\n    height: 150px;\n  }\n  .logo-container h1 {\n    text-align: center;\n    width: 100%;\n    font-size: 36px;\n    padding-bottom: 4px;\n    margin-bottom: 24px;\n    border-bottom: 1px solid hsla(203, 50%, 30%, 0.15);\n  }\n`}\n</style>\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/LoadItems.mdx",
    "content": "# Usage\n\nThere are several ways for configuring gallery items. Here are some examples:\n\n## Basic Example with Template Binding\n\nThe most straightforward way to load items into the gallery is by using an array of type `GalleryItem[]` and applying the template binding.\n\n```ts\nimport { Component, OnInit } from '@angular/core';\nimport { GalleryModule, GalleryItem, ImageItem, VideoItem, YoutubeItem, IframeItem } from 'ng-gallery';\n\n@Component({\n  template: `\n    <gallery [items]=\"images\"></gallery>\n  `,\n  imports: [GalleryModule]\n})\nexport class AppComponent implements OnInit {\n\n  images: GalleryItem[];\n\n  ngOnInit() {\n    // Set items array\n    this.images = [\n      new ImageItem({\n        src: 'IMAGE_SRC_URL',\n        thumb: 'IMAGE_THUMBNAIL_URL'\n      }),\n      new VideoItem({\n        src: 'VIDEO_URL',\n        poster: 'VIDEO_POSTER_URL',\n        thumb: 'IMAGE_THUMBNAIL_URL'\n      }),\n      new YoutubeItem({\n        src: 'VIDEO_ID'\n      }),\n      new IframeItem({\n        src: 'IFRAME_URL',\n        thumb: 'IMAGE_THUMBNAIL_URL'\n      })\n    ];\n  }\n}\n```\n\n## Accessing via Component Reference\n\nAlternatively, you can access the gallery by referencing the `GalleryComponent`.\nTo do this, acquire the component reference using `@ViewChild(GalleryComponent)` and then add items accordingly.\n\n```ts\nimport { Component, ViewChild } from '@angular/core';\nimport { GalleryModule, GalleryComponent, ImageItem, VideoItem, YoutubeItem, IframeItem } from 'ng-gallery';\n\n@Component({\n  template: `\n    <gallery></gallery>\n  `,\n  imports: [GalleryModule]\n})\nexport class AppComponent {\n\n  @ViewChild(GalleryComponent) myGallery: GalleryComponent;\n\n  // Add items individually\n  addItemIndividually() {\n    this.myGallery.addImage({\n      src: 'IMAGE_SRC_URL',\n      thumb: 'IMAGE_THUMBNAIL_URL'\n    });\n\n    this.myGallery.addVideo({\n      src: 'VIDEO_URL',\n      poster: 'VIDEO_POSTER_URL',\n      thumb: 'IMAGE_THUMBNAIL_URL'\n    });\n\n    this.myGallery.addYoutube({\n      src: 'VIDEO_ID'\n    });\n\n    this.myGallery.addIframe({\n      src: 'IFRAME_URL',\n      thumb: 'IMAGE_THUMBNAIL_URL'\n    });\n  }\n\n  // Load a new set of items\n  loadItems() {\n    const items: GalleryItem[] = [\n      new ImageItem({\n        src: 'IMAGE_SRC_URL',\n        thumb: 'IMAGE_THUMBNAIL_URL'\n      }),\n      new VideoItem({\n        src: 'VIDEO_URL',\n        poster: 'VIDEO_POSTER_URL',\n        thumb: 'IMAGE_THUMBNAIL_URL'\n      }),\n      new YoutubeItem({\n        src: 'VIDEO_ID'\n      }),\n      new IframeItem({\n        src: 'IFRAME_URL',\n        thumb: 'IMAGE_THUMBNAIL_URL'\n      })\n    ]\n\n    this.myGallery.load(items);\n  }\n}\n```\n\n## Accessing via `Gallery` Service\n\nTo access the gallery from any location within your application, utilize the `Gallery` service to obtain the `GalleryRef` by its unique identifier.\n\n```ts\nimport { Component, OnInit } from '@angular/core';\nimport { GalleryModule, Gallery, GalleryRef, GalleryItem, ImageItem, VideoItem, YoutubeItem, IframeItem } from 'ng-gallery';\n\n@Component({\n  template: `\n    <gallery id=\"myGallery\"></gallery>\n  `,\n  imports: [GalleryModule]\n})\nexport class AppComponent implements OnInit {\n\n  galleryRef: GalleryRef;\n\n  constructor(private gallery: Gallery) {\n  }\n\n  ngOnInit() {\n    // Get the galleryRef by id\n    this.galleryRef = this.gallery.ref('myGallery');\n  }\n\n  // Add items individually\n  addItemIndividually() {\n    this.galleryRef.addImage({\n      src: 'IMAGE_SRC_URL',\n      thumb: 'IMAGE_THUMBNAIL_URL'\n    });\n\n    this.galleryRef.addVideo({\n      src: 'VIDEO_URL',\n      poster: 'VIDEO_POSTER_URL',\n      thumb: 'IMAGE_THUMBNAIL_URL'\n    });\n\n    this.galleryRef.addYoutube({\n      src: 'VIDEO_ID'\n    });\n\n    this.galleryRef.addIframe({\n      src: 'IFRAME_URL',\n      thumb: 'IMAGE_THUMBNAIL_URL'\n    });\n  }\n\n  // Load a new set of items\n  loadItems() {\n    const items: GalleryItem[] = [\n      new ImageItem({\n        src: 'IMAGE_SRC_URL',\n        thumb: 'IMAGE_THUMBNAIL_URL'\n      }),\n      new VideoItem({\n        src: 'VIDEO_URL',\n        poster: 'VIDEO_POSTER_URL',\n        thumb: 'IMAGE_THUMBNAIL_URL'\n      }),\n      new YoutubeItem({\n        src: 'VIDEO_ID'\n      }),\n      new IframeItem({\n        src: 'IFRAME_URL',\n        thumb: 'IMAGE_THUMBNAIL_URL'\n      })\n    ]\n    this.galleryRef.load(items);\n  }\n}\n```\n\nThese methods provide flexibility in setting up and configuring the gallery in your application.\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/Responsiveness.mdx",
    "content": "# Responsiveness\n\nTo modify the gallery configuration for smaller screens, including adjustments to thumbnail positioning and size,\n[Angular CDK](https://material.angular.io/cdk/layout/overview) provides an effective solution.\n\n\n**Example**\n\n```ts\nimport { Component } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';\nimport { GalleryModule, GalleryConfig, ThumbnailPosition } from 'ng-gallery';\nimport { Observable, map } from 'rxjs';\n\n@Component({\n  selector: 'gallery-example',\n  template: `\n      <gallery *ngIf=\"galleryConfig$ | async; let config\"\n               [items]=\"images$ | async\"\n               [thumbWidth]=\"config.thumbWidth\"\n               [thumbHeight]=\"config.thumbHeight\"\n               [thumbPosition]=\"config.thumbPosition\" />\n  `,\n  imports: [GalleryModule, CommonModule]\n})\nclass MyComponent {\n\n  galleryConfig$: Observable<GalleryConfig>;\n\n  constructor(breakpointObserver: BreakpointObserver) {\n\n    this.galleryConfig$ = breakpointObserver.observe([\n      Breakpoints.HandsetPortrait\n    ]).pipe(\n      map(res => {\n        if (res.matches) {\n          return {\n            thumbPosition: ThumbnailPosition.Top,\n            thumbWidth: 80,\n            thumbHeight: 80\n          };\n        }\n        return {\n          thumbPosition: ThumbnailPosition.Left,\n          thumbWidth: 120,\n          thumbHeight: 90\n        };\n      })\n    );\n  }\n}\n```\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/basic/Bullets.mdx",
    "content": "import { Meta, Canvas } from \"@storybook/addon-docs\";\nimport * as GalleryStories from \"./Gallery.stories\";\n\n<Meta of={GalleryStories}/>\n\n# Gallery Bullets\n\nThe bullets offer a straightforward way to navigate in the gallery.\n\n<Canvas of={GalleryStories.BulletExample}/>\n\n## Bullets position\n\nThere are 2 positioning choices are available for bullets: `top` and `bottom`.\n\n**Example**\n\n```html\n<gallery [items]=\"items\" bullets bulletPosition=\"bottom\" />\n```\n\n\n## Customizing bullet Size\n\nTo change bullets size, use the `bulletSize` input\n\n\n## Disabling bullet Clicks\n\nTo prevent navigating to new items by clicking on bullets, use the following attribute:\n\n```html\n<gallery bullets disableBullets />\n```\n\n\n\n## List of options related to gallery bullets\n\n- bullets\n- bulletSize\n- bulletPosition\n- disableBullets\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/basic/Counter.mdx",
    "content": "import { Meta, Canvas } from \"@storybook/addon-docs\";\nimport * as GalleryStories from \"./Gallery.stories\";\n\n<Meta of={GalleryStories}/>\n\n# Gallery Counter\n\nIndicates the number of the active value and the total amount of items in the gallery.\n\n<Canvas of={GalleryStories.CounterExample}/>\n\n\n\n## Counter position\n\nThere are 2 choices for positioning the counter, `top` and `bottom`.\n\n**Example**\n\n```html\n<gallery counter counterPosition=\"bottom\" />\n```\n\n\n\n## List of options related to gallery counter\n\n- counter\n- counterPosition\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/basic/Gallery.stories.ts",
    "content": "import type { Meta, StoryObj } from '@storybook/angular';\nimport { applicationConfig } from '@storybook/angular';\nimport { provideAnimations } from '@angular/platform-browser/animations';\nimport {\n  CounterPosition,\n  BulletsPosition,\n  GalleryComponent,\n  IMAGE_SIZE,\n  LOADING_ATTR,\n  LOADING_STRATEGY,\n  ORIENTATION,\n  THUMB_POSITION,\n} from 'ng-gallery';\nimport { getHDImages } from '../pixabay/pixabay.service';\n\nimport 'hammerjs';\n\nconst meta: Meta<GalleryComponent> = {\n  title: 'Documentations/Gallery',\n  component: GalleryComponent,\n  tags: ['autodocs'],\n  render: (args: GalleryComponent) => ({\n    props: {\n      ...args,\n    },\n  }),\n  decorators: [\n    applicationConfig({\n      providers: [provideAnimations()],\n    }),\n  ],\n  args: {\n    id: 'root',\n    scrollBehavior: 'smooth',\n    imageSize: IMAGE_SIZE.Contain,\n    thumbImageSize: IMAGE_SIZE.Cover,\n    bulletPosition: BulletsPosition.Bottom,\n    counterPosition: CounterPosition.Top,\n    orientation: ORIENTATION.Horizontal,\n    loadingAttr: LOADING_ATTR.Lazy,\n    loadingStrategy: LOADING_STRATEGY.Default,\n    thumbPosition: THUMB_POSITION.Bottom,\n    scrollEase: {\n      x1: 0.42,\n      y1: 0,\n      x2: 0.58,\n      y2: 1,\n    },\n    bulletSize: 6,\n    thumbWidth: 120,\n    thumbHeight: 90,\n    autoplayInterval: 3000,\n    scrollDuration: 468,\n    resizeDebounceTime: 0,\n    nav: true,\n    bullets: true,\n    disableBullets: false,\n    loop: true,\n    debug: false,\n    thumbs: true,\n    counter: true,\n    autoplay: false,\n    autoHeight: false,\n    itemAutosize: false,\n    disableThumbs: false,\n    detachThumbs: false,\n    thumbAutosize: false,\n    disableScroll: false,\n    thumbCentralized: false,\n    disableThumbScroll: false,\n    disableMouseScroll: false,\n    disableThumbMouseScroll: false,\n  },\n  // Disables the long useless control description\n  argTypes: {\n    scrollBehavior: {\n      control: 'radio',\n      options: ['smooth', 'auto'],\n      table: {\n        defaultValue: { summary: 'smooth' },\n      },\n    },\n    imageSize: {\n      table: {\n        defaultValue: { summary: 'contain' },\n      },\n    },\n    thumbImageSize: {\n      table: {\n        defaultValue: { summary: 'cover' },\n      },\n    },\n    bulletPosition: {\n      table: {\n        defaultValue: { summary: 'bottom' },\n      },\n    },\n    counterPosition: {\n      table: {\n        defaultValue: { summary: 'top' },\n      },\n    },\n    orientation: {\n      table: {\n        defaultValue: { summary: 'horizontal' },\n      },\n    },\n    loadingAttr: {\n      table: {\n        defaultValue: { summary: 'lazy' },\n      },\n    },\n    loadingStrategy: {\n      table: {\n        defaultValue: { summary: 'default' },\n      },\n    },\n    thumbPosition: {\n      table: {\n        defaultValue: { summary: 'bottom' },\n      },\n    },\n    thumbCentralized: {\n      table: {\n        defaultValue: { summary: 'default' },\n      },\n    },\n    scrollEase: {\n      table: {\n        defaultValue: { summary: '{ x1: 0.42, y1: 0, x2: 0.58, y2: 1 }' },\n      },\n    },\n    nav: {\n      table: {\n        defaultValue: { summary: 'true' },\n      },\n    },\n    thumbs: {\n      table: {\n        defaultValue: { summary: 'true' },\n      },\n    },\n    loop: {\n      table: {\n        defaultValue: { summary: 'true' },\n      },\n    },\n    counter: {\n      table: {\n        defaultValue: { summary: 'true' },\n      },\n    },\n    bulletSize: {\n      table: {\n        defaultValue: { summary: '6' },\n      },\n    },\n    thumbWidth: {\n      table: {\n        defaultValue: { summary: '120' },\n      },\n    },\n    thumbHeight: {\n      table: {\n        defaultValue: { summary: '90' },\n      },\n    },\n    autoplayInterval: {\n      table: {\n        defaultValue: { summary: '3000' },\n      },\n    },\n    scrollDuration: {\n      table: {\n        defaultValue: { summary: '468' },\n      },\n    },\n    resizeDebounceTime: {\n      table: {\n        defaultValue: { summary: '0' },\n      },\n    },\n    error: {\n      control: false,\n    },\n    indexChange: {\n      control: false,\n    },\n    itemsChange: {\n      control: false,\n    },\n    itemClick: {\n      control: false,\n    },\n    thumbClick: {\n      control: false,\n    },\n    playingChange: {\n      control: false,\n    },\n    galleryRef: {\n      control: false,\n    },\n    load: {\n      control: false,\n    },\n    add: {\n      control: false,\n    },\n    addImage: {\n      control: false,\n    },\n    addVideo: {\n      control: false,\n    },\n    addIframe: {\n      control: false,\n    },\n    addYoutube: {\n      control: false,\n    },\n    remove: {\n      control: false,\n    },\n    next: {\n      control: false,\n    },\n    prev: {\n      control: false,\n    },\n    set: {\n      control: false,\n    },\n    reset: {\n      control: false,\n    },\n    play: {\n      control: false,\n    },\n    stop: {\n      control: false,\n    },\n  },\n};\n\nexport default meta;\ntype Story = StoryObj<GalleryComponent>;\n\n/** First Example for gallery */\nexport const Lab: Story = {\n  render: (args: GalleryComponent, { loaded: { items } }) => ({\n    props: {\n      ...args,\n      items,\n    },\n    template: `\n      <gallery [items]=\"items\"\n               [orientation]=\"orientation\"\n               [scrollBehavior]=\"scrollBehavior\"\n               [scrollDuration]=\"scrollDuration\"\n               [disableScroll]=\"disableScroll\"\n               [disableMouseScroll]=\"disableMouseScroll\"\n               [autoHeight]=\"autoHeight\"\n               [itemAutosize]=\"itemAutosize\"\n               [thumbs]=\"thumbs\"\n               [thumbImageSize]=\"thumbImageSize\"\n               [thumbPosition]=\"thumbPosition\"\n               [thumbCentralized]=\"thumbCentralized\"\n               [thumbWidth]=\"thumbWidth\"\n               [thumbHeight]=\"thumbHeight\"\n               [disableThumbs]=\"disableThumbs\"\n               [detachThumbs]=\"detachThumbs\"\n               [thumbAutosize]=\"thumbAutosize\"\n               [disableThumbScroll]=\"disableThumbScroll\"\n               [disableThumbMouseScroll]=\"disableThumbMouseScroll\"\n               [bullets]=\"bullets\"\n               [bulletPosition]=\"bulletPosition\"\n               [bulletSize]=\"bulletSize\"\n               [disableBullets]=\"disableBullets\"\n               [resizeDebounceTime]=\"resizeDebounceTime\"\n               [nav]=\"nav\"\n               [loop]=\"loop\"\n               [debug]=\"debug\"\n               [counter]=\"counter\"\n               [counterPosition]=\"counterPosition\"\n               [autoplay]=\"autoplay\"\n               [autoplayInterval]=\"autoplayInterval\"\n               [imageSize]=\"imageSize\"\n               [loadingAttr]=\"loadingAttr\"\n               [loadingStrategy]=\"loadingStrategy\"/>\n    `,\n  }),\n  loaders: [\n    async () => ({\n      items: await getHDImages('jet fighter'),\n    }),\n  ],\n};\n\nexport const ThumbPositionExample: Story = {\n  parameters: { controls: { exclude: /.*/g } },\n  render: (args: GalleryComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `<gallery [items]=\"items\" thumbs thumbPosition=\"bottom\"/>`,\n  }),\n  loaders: [async () => ({ items: await getHDImages('street') })],\n};\n\nexport const ThumbAutosizeExample: Story = {\n  parameters: { controls: { exclude: /.*/g } },\n  render: (args: GalleryComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `<gallery [items]=\"items\" thumbs thumbAutosize/>`,\n  }),\n  loaders: [async () => ({ items: await getHDImages('roads') })],\n};\n\nexport const ThumbDetachedExample: Story = {\n  parameters: { controls: { exclude: /.*/g } },\n  render: (args: GalleryComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `<gallery [items]=\"items\" thumbs detachThumbs/>`,\n  }),\n  loaders: [async () => ({ items: await getHDImages('flowers') })],\n};\n\nexport const ThumbViewExample: Story = {\n  parameters: { controls: { exclude: /.*/g } },\n  render: (args: GalleryComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `<gallery [items]=\"items\" thumbs thumbCentralized/>`,\n  }),\n  loaders: [async () => ({ items: await getHDImages('landscape') })],\n};\n\nexport const AutoplayExample: Story = {\n  parameters: { controls: { exclude: /.*/g } },\n  render: (args: GalleryComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `<gallery [items]=\"items\" autoplay />`,\n  }),\n  loaders: [async () => ({ items: await getHDImages('airplane') })],\n};\n\nexport const BulletExample: Story = {\n  parameters: { controls: { exclude: /.*/g } },\n  render: (args: GalleryComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `<gallery [items]=\"items\" bullets />`,\n  }),\n  loaders: [async () => ({ items: await getHDImages('football') })],\n};\n\nexport const CounterExample: Story = {\n  parameters: { controls: { exclude: /.*/g } },\n  render: (args: GalleryComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `<gallery [items]=\"items\" counter />`,\n  }),\n  loaders: [async () => ({ items: await getHDImages('basketball') })],\n};\n\nexport const NavExample: Story = {\n  parameters: { controls: { exclude: /.*/g } },\n  render: (args: GalleryComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `<gallery [items]=\"items\"/>`,\n  }),\n  loaders: [async () => ({ items: await getHDImages('lions') })],\n};\n\nexport const SliderExample: Story = {\n  parameters: { controls: { exclude: /.*/g } },\n  render: (args: GalleryComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `<gallery [items]=\"items\"/>`,\n  }),\n  loaders: [async () => ({ items: await getHDImages('chips') })],\n};\n\nexport const SliderBehaviorExample: Story = {\n  parameters: { controls: { exclude: /.*/g } },\n  render: (args: GalleryComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `<gallery [items]=\"items\" scrollBehavior=\"auto\"/>`,\n  }),\n  loaders: [async () => ({ items: await getHDImages('chips') })],\n};\n\nexport const NavWithLoopExample: Story = {\n  parameters: { controls: { exclude: /.*/g } },\n  render: (args: GalleryComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `<gallery [items]=\"items\" loop/>`,\n  }),\n  loaders: [async () => ({ items: await getHDImages('tigers') })],\n};\n\nexport const SliderDirectionExample: Story = {\n  parameters: { controls: { exclude: /.*/g } },\n  render: (args: GalleryComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `<gallery [items]=\"items\" orientation=\"vertical\" />`,\n  }),\n  loaders: [async () => ({ items: await getHDImages('tigers') })],\n};\n\nexport const ImageSizeExample: Story = {\n  parameters: { controls: { exclude: /.*/g } },\n  render: (args: GalleryComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `<gallery [items]=\"items\" imageSize=\"cover\" />`,\n  }),\n  loaders: [async () => ({ items: await getHDImages('tigers') })],\n};\n\nexport const AutoHeightExample: Story = {\n  parameters: { controls: { exclude: /.*/g } },\n  render: (args: GalleryComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `<gallery [items]=\"items\" autoHeight />`,\n  }),\n  loaders: [async () => ({ items: await getHDImages('tigers') })],\n};\n\nexport const ItemAutosizeExample: Story = {\n  parameters: { controls: { exclude: /.*/g } },\n  render: (args: GalleryComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `<gallery [items]=\"items\" itemAutosize loadingStrategy=\"preload\" loadingAttr=\"eager\" />`,\n  }),\n  loaders: [async () => ({ items: await getHDImages('ship') })],\n};\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/basic/Navigation.mdx",
    "content": "import { Meta, Canvas } from \"@storybook/addon-docs\";\nimport * as GalleryStories from \"./Gallery.stories\";\n\n<Meta of={GalleryStories}/>\n\n# Gallery Nav\n\nThis feature provide navigation buttons designed to facilitate movement between next and previous items.\n\n<Canvas of={GalleryStories.NavExample}/>\n\n\n### Navigation loop\n\n<Canvas of={GalleryStories.NavWithLoopExample}/>\n\n\n## List of options related to gallery nav\n\n- nav\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/basic/Player.mdx",
    "content": "import { Meta, Canvas } from \"@storybook/addon-docs\";\nimport * as GalleryStories from \"./Gallery.stories\";\n\n<Meta of={GalleryStories}/>\n\n# Gallery Player\n\nThis feature facilitates automatic cycling through items like a slideshow.\n\n## Using the component inputs\n\nTo initiate the player, use the `autoplay` input.\nYou can specify the player's interval using the `autoplayInterval` input, which accepts a value in milliseconds. The default interval is set to 3000.\n\n<Canvas of={GalleryStories.AutoplayExample}/>\n\n\nYou can also toggle playing by binding the input to a boolean value, as the following example:\n\n```html\n<gallery [autoplay]=\"isPlaying\"/>\n```\n\n\n## Using `GalleryComponent` reference\n\nObtain the gallery component reference using `ViewChild(GalleryComponent)`. This reference grants access to the component's functions.\n\n\n**Example**\n\n```ts\nimport { Component, ViewChild } from '@angular/core';\nimport { GalleryModule, GalleryComponent } from 'ng-gallery';\n\n@Component({\n  template: `\n    <gallery [items]=\"items\"/>\n  `,\n  imports: [GalleryModule]\n})\nexport class AppComponent {\n\n  @ViewChild(GalleryComponent) myGallery: GalleryComponent;\n\n  play() {\n    this.myGallery.play();\n  }\n\n  stop() {\n    this.myGallery.stop();\n  }\n}\n```\n\n## Using `GalleryRef` reference\n\nAlternatively, you can access the `GalleryRef` by its identifier, providing a flexible means to start or stop the player from any part of your code.\n\n\n**Example**\n\n```ts\nimport { Component, OnInit } from '@angular/core';\nimport { GalleryModule, Gallery, GalleryRef } from 'ng-gallery';\n\n@Component({\n  template: `\n    <gallery id=\"myGallery\"></gallery>\n  `,\n  imports: [GalleryModule]\n})\nexport class AppComponent implements OnInit {\n\n  myGalleryRef: GalleryRef;\n\n  constructor(private gallery: Gallery) {\n  }\n\n  ngOnInit() {\n    this.myGalleryRef = this.gallery.ref('myGallery');\n  }\n\n  play() {\n    this.myGalleryRef.play();\n  }\n\n  stop() {\n    this.myGalleryRef.stop();\n  }\n}\n```\n\n\n\n## List of options related to gallery player\n\n- autoplay\n- autoplayInterval\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/basic/Slider.mdx",
    "content": "import {Meta, Canvas} from \"@storybook/addon-docs\";\nimport * as GalleryStories from \"./Gallery.stories\";\n\n<Meta of={GalleryStories}/>\n\n# Slider\n\nIn this section, we'll cover the main options of the main slider which contains the items\n\n<Canvas of={GalleryStories.SliderExample}/>\n\n\nThe slider navigates through items using scrolling mechanism, it can be scrolled using the mouse wheel, touchpad on desktop or gestures on touch devices.\n\n### Slide using mouse drag\n\nThe slider can be scrolled using mouse drag which requires hammerjs\n\n> Sliding using mouse drag is optional, the gallery doesn't depend on hammerjs\n> This feature is turned on by default when hammerjs is loaded in the app.\n> You can turn off this feature by adding `mouseSlidingDisabled` attribute.\n\n**Example**\n\n```html\n`<ng-gallery mouseSlidingDisabled />`\n```\n\n\n### Orientation\n\nThere are two choices for the gallery orientation `horizontal` and `vertical`, the default is `horizontal`.\n\n**Example of a vertical orientation**\n\n<Canvas of={GalleryStories.SliderDirectionExample}/>\n\n\n\n\n### Loading strategy\n\nThere are 3 choices for loading strategy of the main slider\n\n- `default` renders only the active item, the previous item and the next item\n- `lazy`: renders only the active item\n- `preload` renders all the items, this option is required for `thumbAutoSize` is enabled\n\n**Note**\n\n> `preload` should be chosen if `itemAutoSize` is turned on\n\n\n\n\n### Image size\n\nBy default, the image in item is contained in the slide, to make the image covers the slide\nSets the object-fit style applied on items' images\n\n<Canvas of={GalleryStories.ImageSizeExample}/>\n\n\n\n\n### Auto-height\n\nAutomatically adjusts the gallery's height to fit the content\n\n<Canvas of={GalleryStories.AutoHeightExample}/>\n\n\n\n\n\n### Scroll behavior\n\nThere are two distinct navigation modes to choose from:\n\n- **Smooth:** When you opt for the `smooth` mode which is the default, the gallery will smoothly scroll to the desired item,\nwith the scrolling duration determined by the `slidingDuration`.\n- **Auto:** If you select the `auto` mode, the gallery will instantaneously transition to the desired item without any intermediate scrolling.\n\n**Example**\n\n<Canvas of={GalleryStories.SliderBehaviorExample}/>\n\n\n\n\n### Autosize feature\n\nWhen Use `autosize` attribute is set, it allows each thumbnail to fit its content size.\n\n\n**Example**\n\n<Canvas of={GalleryStories.ItemAutosizeExample}/>\n\n\nYou can turn on this feature like `<gallery autosize loadingStrategy=\"preload\" loadingAttr=\"eager\"/>\n\n- In `horizontal` slide direction, item's width will automatically fit its content width.\n- In `vertical` slide direction, item's height will automatically fit its content height.\n\n> **Note**\n> - Does not work when `autoHeight` is on\n> - Does not work properly unless `loadingAttr=\"eager\"`\n> - Does not work properly unless `loadingStrategy=\"preload\"`\n\n\n\n\n## List of options related to gallery main slider\n\n- orientation\n- scrollEase\n- scrollBehavior\n- disableScroll\n- disableMouseScroll\n- loadingStrategy\n- imageSize\n- debug\n- resizeDebounceTime\n- autoHeight\n- autoItemSize\n- loop\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/basic/Thumbnails.mdx",
    "content": "import { Meta, Canvas } from \"@storybook/addon-docs\";\nimport * as GalleryStories from \"./Gallery.stories\";\n\n<Meta of={GalleryStories}/>\n\n# Gallery Thumbnails\n\nThumbnails are an integral part of the gallery component, offering multiple customization options for appearance and behavior.\n\n\n\n## Displaying thumbnails\n\nTo display thumbnails, add the following attribute:\n\n```html\n<gallery thumbs />\n```\n\n\n## Thumbnail position\n\nThere are 4 positioning choices are available for thumbnails: `top`, `left`, `bottom` and `right`.\n\nThe chosen position also dictates the slide direction: horizontal for `top` and `bottom`, and vertical for `left` and `right`.\n\n<Canvas of={GalleryStories.ThumbPositionExample}/>\n\n\n\n## Customizing thumbnail size\n\nThumbnails can be resized using the `thumbWidth` and `thumbHeight` inputs:\n\n- For `top` or `bottom` positions, `thumbHeight` determines the height of the thumbnails' slider.\n- For `left` or `right` positions, `thumbWidth` determines the width of the thumbnails' slider.\n\n\n## Automatic thumbnail sizing\n\nThe `thumbAutosize` attribute enables automatic thumbnail sizing, ensuring each thumbnail fits its content size.\n\n- In `top` or `bottom` positions, the width of each thumbnail adjusts to its content.\n- In `left` or `right` positions, the height of each thumbnail adjusts to its content.\n\n<Canvas of={GalleryStories.ThumbAutosizeExample}/>\n\n\n## Disabling thumbnail clicks\n\nTo prevent navigating to new items by clicking on thumbnails, use the following attribute:\n\n```html\n<gallery disableThumbs />\n```\n\n\n\n## Detaching thumbnails\n\nTypically, thumbnails adjust to centralize the active item. To disable this behavior `detachThumbs` as follows:\n\n<Canvas of={GalleryStories.ThumbDetachedExample}/>\n\n\n\n## Centralized thumbnails\n\nBy default, the active thumbnail is centrally positioned in the slider. However, if it's located at the beginning or end, it remains in place. To enforce centralization, utilize the `thumbView` attribute:\n\n<Canvas of={GalleryStories.ThumbViewExample}/>\n\n\n\n## List of options related to gallery thumbnails\n\n- thumbs\n- thumbPosition\n- thumbWidth\n- thumbHeight\n- thumbAutosize\n- thumbCentralized\n- thumbImageSize\n- detachThumbs\n- disableThumbs\n- disableThumbMouseScroll\n- disableThumbMouseScroll\n\nThe API table is available [here](/docs/documentations-gallery--lab).\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/custom-templates/CustomTemplates.stories.ts",
    "content": "import type { Meta, StoryObj } from '@storybook/angular';\nimport { applicationConfig, moduleMetadata } from '@storybook/angular';\nimport { provideAnimations } from '@angular/platform-browser/animations';\nimport { GalleryModule } from 'ng-gallery';\nimport { CustomTemplateComponent } from './custom-template.component';\nimport {\n  getHDImages,\n  getHDImagesForCustomTemplate,\n} from '../pixabay/pixabay.service';\n\nimport 'hammerjs';\n\nconst meta: Meta<CustomTemplateComponent> = {\n  title: 'Documentations/CustomTemplates',\n  component: CustomTemplateComponent,\n  // tags: ['autodocs'],\n  render: (args: CustomTemplateComponent) => ({\n    props: {\n      ...args,\n    },\n  }),\n  decorators: [\n    moduleMetadata({\n      imports: [GalleryModule],\n    }),\n    applicationConfig({\n      providers: [provideAnimations()],\n    }),\n  ],\n};\n\nexport default meta;\ntype Story = StoryObj<CustomTemplateComponent>;\n\nexport const ImageTemplateExample: Story = {\n  render: (args: CustomTemplateComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `\n      <gallery [items]=\"items\">\n        <div *galleryImageDef=\"let item\" class=\"my-image-overlay\">\n          {{ item?.alt }}\n        </div>\n      </gallery>\n    `,\n  }),\n  loaders: [async () => ({ items: await getHDImages('sea') })],\n};\n\nexport const ItemTemplateExample: Story = {\n  render: (args: CustomTemplateComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `\n      <gallery [items]=\"items\">\n        <div *galleryItemDef=\"let item\" class=\"my-item-template\">\n          <img [src]=\"item?.src\">\n        </div>\n      </gallery>\n    `,\n    styles: [\n      `\n      img {\n        object-fit: cover;\n        border: 10px white solid;\n        width: 400px;\n        height: 400px;\n      }\n    `,\n    ],\n  }),\n  loaders: [\n    async () => ({ items: await getHDImagesForCustomTemplate('sand') }),\n  ],\n};\n\nexport const ThumbTemplateExample: Story = {\n  render: (args: CustomTemplateComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `\n      <gallery [items]=\"items\" thumbs>\n        <div *galleryThumbDef=\"let thumb\" class=\"my-thumb-overlay\">\n          {{ thumb?.alt }}\n        </div>\n      </gallery>\n    `,\n  }),\n  loaders: [async () => ({ items: await getHDImages('sea') })],\n};\n\nexport const BoxTemplateExample: Story = {\n  render: (args: CustomTemplateComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `\n      <gallery [items]=\"items\">\n        <div *galleryBoxDef=\"let state; config\" class=\"my-box\">\n          <div>This is fixed overlay template</div>\n          <img src=\"https://user-images.githubusercontent.com/8130692/36171173-ad0da54c-1112-11e8-85bf-843c5f70efdc.png\">\n        </div>\n      </gallery>\n    `,\n    styles: [\n      `\n      .my-box {\n        width: 200px;\n        height: 50px;\n        color: white;\n      }\n      img {\n        position: absolute;\n        z-index: 999;\n        width: 40px;\n        height: 40px;\n        top: 20px;\n        left: 20px;\n      }\n    `,\n    ],\n  }),\n  loaders: [async () => ({ items: await getHDImages('sea') })],\n};\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/custom-templates/CustomTemplatesUsage.mdx",
    "content": "import {Meta, Canvas} from \"@storybook/addon-docs\";\nimport * as CustomTemplatesStories from \"./CustomTemplates.stories\";\n\n<Meta of={CustomTemplatesStories}/>\n\n# Custom Templates\n\n\n\n\nThe directives are: `*galleryItemDef`, `*galleryImageDef`, `*galleryThumbDef`, `*galleryBoxDef`\n\n## GalleryImageDef\n\n`*galleryImageDef`: applies only on image items - the image will still be displayed, but this adds an custom inner template, useful to add text overlay on top of the image.\n\n\n<Canvas of={CustomTemplatesStories.ImageTemplateExample}/>\n\n**Example:**\n\n```html\n<gallery>\n  <ng-container *galleryImageDef=\"let item; let active = active\">\n    <div *ngIf=\"active\" class=\"item-text\">\n      {{ item?.alt }}\n    </div>\n  </ng-container>\n</gallery>\n```\n\n## GalleryThumbDef\n\n`*galleryThumbDef`: applies on all thumbnails types, image will still displayed, but adds an custom inner template.\n\n\n<Canvas of={CustomTemplatesStories.ThumbTemplateExample}/>\n\n**Example:** the following adds a Youtube icon on Youtube thumbnail and video icon on video thumbnails:\n\n```html\n<gallery>\n  <ng-container *galleryThumbDef=\"let item; let type = type\">\n    <span *ngIf=\"type === 'youtube'\" class=\"item-type\">\n      <fa-icon [icon]=\"youtubeIcon\" size=\"lg\"></fa-icon>\n    </span>\n    <span *ngIf=\"type === 'video'\" class=\"item-type\">\n      <fa-icon [icon]=\"videoIcon\" size=\"lg\"></fa-icon>\n    </span>\n  </ng-container>\n</gallery>\n```\n\n\n## GalleryItemDef\n\n`*galleryItemDef`: overrides gallery item template, useful if you want create your own gallery template for any type from scratch.\n\n<Canvas of={CustomTemplatesStories.ItemTemplateExample}/>\n\n**Example:**\n\n```html\n<gallery>\n  <div *galleryItemDef=\"let item; let type = type\">\n    <div *ngIf=\"type === 'my-image-template'\">\n      <img [src]=\"item.src\">\n    </div>\n    <div *ngIf=\"type === 'my-video-template'\">\n      <video>\n        <source src=\"item.src\">\n      </video>\n    </div>\n  </div>\n</gallery>\n```\n\n > When using `galleryItemDef` you will need to add the items differently\n\n```ts\ngalleryRef.add({\n  type: 'my-image-template',\n  data: {\n    src: 'IMAGE_SRC_URL',\n    thumb: 'IMAGE_THUMBNAIL_URL'\n    alt: 'Test'\n  }\n})\n\n// or using items array\nconst items: GalleryItem[] = [\n  {\n     type: 'my-image-template'\n     data: {\n       src: 'IMAGE_SRC_URL',\n       thumb: 'IMAGE_THUMBNAIL_URL'\n     }\n  }\n  // more items\n];\n```\n\n\n#### Additional context properties that comes with `GalleryItemDef`, `GalleryImageDef` and `GalleryThumbDef` are:\n\n```ts\n{\n  /** Index of the item. */\n  index?: number;\n\n  /** Type of the item, default ones are 'image', 'video', 'youtube', 'iframe' */\n  type?: string;\n\n  /** True if this item is the active one. */\n  active?: boolean;\n\n  /** The number of total items. */\n  count?: number;\n\n  /** True if this item is first. */\n  first?: boolean;\n\n  /** True if this item is last. */\n  last?: boolean;\n}\n```\n\n## GalleryBoxDef\n\n`*galleryBoxDef` Adds a single static template on top of the gallery items\n\n<Canvas of={CustomTemplatesStories.BoxTemplateExample}/>\n\n```html\n<gallery>\n  <div *galleryBoxDef=\"let state = state; let config = config\">\n    {{ state.currIndex + 1 }} / {{ state.items.length }}\n  </div>\n</gallery>\n```\n\nAvailable context variables are `state` and `config`\n\n> You can use these directives on DOM elements and components or even `ng-container` and `ng-template`\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/custom-templates/custom-template.component.ts",
    "content": "import { Component, ChangeDetectionStrategy, Input } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { GalleryModule, GalleryItem, } from 'ng-gallery';\nimport { Observable } from 'rxjs';\n\n/**\n * This section demonstrate how to extend the image template, like displaying a text over the image item\n */\n@Component({\n  selector: 'custom-templates-example',\n  template: `\n    <gallery [items]=\"items\">\n      <ng-container *galleryImageDef=\"let item\">\n        <div class=\"item-text\">\n          {{ item?.alt }}\n        </div>\n      </ng-container>\n    </gallery>\n  `,\n  styles: [`\n    .item-text {\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      margin: 3em auto 0;\n      width: 100%;\n      max-width: 500px;\n      padding: 20px 25px;\n      text-align: justify;\n      letter-spacing: 1px;\n      filter: drop-shadow(0px 0px 2px rgba(0, 0, 0, 0.3));\n      background: #ffffffd9;\n      color: black;\n      border-radius: 8px;\n    }\n  `],\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  imports: [CommonModule, GalleryModule]\n})\nexport class CustomTemplateComponent {\n\n  @Input() items: GalleryItem[];\n\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/lightbox/CustomTemplates.mdx",
    "content": "import {Meta, Canvas} from \"@storybook/addon-docs\";\nimport * as LightboxStories from \"./Lightbox.stories\";\n\n<Meta of={LightboxStories}/>\n\n# Custom templates with the Lightbox\n\nThe usage is similar to usage of custom templates in the gallery, however, we need to get a reference to the template and pass it to the gallery config.\n\n**Example:**\n\n```ts\nimport {Component, ViewChild } from '@angular/core';\nimport { Gallery, GalleryImageDef } from 'ng-gallery';\n\n@Component({ ... })\nexport class LightboxExampleComponent {\n  \n  // Reference to the image template\n  @ViewChild(GalleryImageDef) imageDef: GalleryImageDef;\n\n  constructor(public gallery: Gallery) {\n  }\n\n  ngAfterViewInit() {\n    this.gallery.ref('lightbox', {\n      imageTemplate: this.imageDef.templateRef\n    }).load(this.items);\n  }\n}\n```\n\nin the template:\n\n```html\n<div *galleryImageDef=\"let item\">\n  {{ item.alt }}\n</div>\n```\n\nThe templates can also be set using `galleryRef.setConfig()` as needed\n\n```ts\nthis.galleryRef.setConfig({\n  thumbTemplate: this.thumbDef.templateRef,\n  boxTemplate: this.boxDef.templateRef\n});\n```\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/lightbox/Gallerize.mdx",
    "content": "import {Meta, Canvas} from \"@storybook/addon-docs\";\nimport * as GalleryStories from \"./Lightbox.stories\";\n\n<Meta of={GalleryStories}/>\n\n\n# Gallerize\n\n<Canvas of={GalleryStories.GallerizeExample}/>\n\n### Basic usage\n\nWrap elements inside a `gallerize` directive\n\n```html\n<div class=\"container\" gallerize>\n   <img src=\"img/image-1\">\n   <img src=\"img/image-2\">\n   <img src=\"img/image-3\">\n   <img src=\"img/image-4\">\n</div>\n```\nYou can also use normal element background instead of `img`\n\n```html\n<div class=\"container\" gallerize>\n   <div [style.background]=\"'url(img/image-1)'\"></div>\n   <div [style.background]=\"'url(img/image-2)'\"></div>\n   <div [style.background]=\"'url(img/image-3)'\"></div>\n   <div [style.background]=\"'url(img/image-4)'\"></div>\n</div>\n```\n\n### Define image's full and thumbnail sources\n\nUse the attributes `imageSrc` and `thumbSrc` to set image full size and thumbnail size\n\n```html\n<div class=\"container\"\n     gallerize>\n  <img *ngFor=\"let image of images\"\n       [src]=\"image.thumb\"\n       [attr.imageSrc]=\"image.src\"\n       [attr.thumbSrc]=\"image.thumb\">\n</div>\n```\n\n### Gallerize specific elements\n\nUse the input `[selector]` to gallerize specific elements\n\n```html\n<div class=\"container\"\n     gallerize\n     selector=\".gallery-img\">\n  <div *ngFor=\"let image of images\"\n       class=\"gallery-img\"\n       [style.backgroundImage]=\"'url(' + image.thumb + ')'\"\n       [attr.imageSrc]=\"image.src\"\n       [attr.thumbSrc]=\"image.thumb\"></div>\n</div>\n```\n\n### Multiple galleries\n\nTo use multiple galleries, just set a unique id, e.g. `gallerize=\"gallery-123\"`\n\n```html\n<h3>Gallery 1</h3>\n\n<div class=\"container\"\n     gallerize=\"gallery-1\">\n<img *ngFor=\"let image of data\"\n      [src]=\"image.previewUrl\"\n      [attr.imageSrc]=\"image.srcUrl\"\n      [attr.thumbSrc]=\"image.previewUrl\">\n</div>\n\n<h3>Gallery 2</h3>\n\n<div class=\"container\"\n     gallerize=\"gallery-2\">\n<img *ngFor=\"let image of data2\"\n      [src]=\"image.previewUrl\"\n      [attr.imageSrc]=\"image.srcUrl\"\n      [attr.thumbSrc]=\"image.previewUrl\">\n</div>\n```\n\nHere is a [multiple gallerize stackblitz](https://stackblitz.com/edit/ngx-gallery-multiple-gallerize).\n\n***\n\n### Gallerize on `<gallery>` component\n\n**Gallerize** can also be used on `<gallery>` component, This will trigger the lightbox on a gallery item's click.\n\n<Canvas of={GalleryStories.GallerizeGalleryExample}/>\n\nHere is a [Gallerize on gallery stackblitz](https://stackblitz.com/edit/ngx-gallery-nqudkt?file=src%2Fapp%2Fhome%2Fhome.component.html)\n\n***\n\n### Set custom config when using `gallerize`\n\n\n```ts\nGalleryModule.withConfig({\n  thumbPosition: 'bottom'\n}),\n```\nHere is a demo https://stackblitz.com/edit/ngx-gallery-multiple-gallerize\n\n```ts\nconstructor(gallery: Gallery) {\n  gallery.ref('gallery-1').setConfig({\n    thumbPosition: 'bottom'\n  })\n}\n```\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/lightbox/GettingStarted.mdx",
    "content": "import {Meta, Canvas} from \"@storybook/addon-docs\";\nimport * as LightboxStories from \"./Lightbox.stories\";\n\n<Meta of={LightboxStories}/>\n\n## Installation\n\n**NPM**\n\n```bash\nnpm i ng-gallery @angular/cdk\n```\n\n## Usage\n\n- Import the CDK overlay-prebuilt CSS file in your global styles.\n\n```css\n@import '~@angular/cdk/overlay-prebuilt.css';\n```\n\n > If you're already using Material, you can skip importing the prebuilt overlay styles.\n\n- For lightbox usage, see [Lightbox Usage](https://github.com/MurhafSousli/ngx-gallery/wiki/Lightbox-Usage) | [Lightbox Usage Demo](https://ngx-gallery.netlify.app/#/lightbox).\n- For gallerize usage, see [Gallerize usage](https://github.com/MurhafSousli/ngx-gallery/wiki/Gallerize-Examples) | [Gallerize Usage Demo](https://ngx-gallery.netlify.app/#/gallerize).\n\n\n\n### Global Configuration\n\nTo set global configuration that applies on all lightbox overlays across the app, set the config value using `LIGHTBOX_CONFIG` token\n\n**Example:**\n\n```ts\nimport { LIGHTBOX_CONFIG, LightboxConfig } from 'ng-gallery/lightbox';\n\nbootstrapApplication(AppComponent, {\n  providers: [\n    {\n      provide: LIGHTBOX_CONFIG,\n      useValue: {\n        keyboardShortcuts: false,\n        exitAnimationTime: 1000\n      } as LightboxConfig\n    }\n  ]\n})\n```\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/lightbox/Lightbox.mdx",
    "content": "import {Meta, Canvas} from \"@storybook/addon-docs\";\nimport * as LightboxStories from \"./Lightbox.stories\";\n\n<Meta of={LightboxStories}/>\n\n# Lightbox\n\n<Canvas of={LightboxStories.LightboxExample} source={'hidden'}/>\n\nThe usage of the lightbox is pretty straight forward\n\n### 1. Create gallery items\n\n```ts\n// Assume you have the following data\nconst data = [\n  {\n    srcUrl: 'https://preview.ibb.co/jrsA6R/img12.jpg',\n    previewUrl: 'https://preview.ibb.co/jrsA6R/img12.jpg'\n  },\n  {\n    srcUrl: 'https://preview.ibb.co/kPE1D6/clouds.jpg',\n    previewUrl: 'https://preview.ibb.co/kPE1D6/clouds.jpg'\n  }\n];\n\n// Map the data to gallery image items\nlet items = data.map(item =>\n  new ImageItem({ src: item.srcUrl, thumb: item.previewUrl })\n);\n```\n\n### 2. Load them in the gallery\n\n```ts\nthis.gallery.ref().load(this.items);\n```\n\n### 3. Add `[lightbox]` on each thumbnail in your template\n\n```html\n<img *ngFor=\"let item of items; index as i\"\n     [lightbox]=\"i\"\n     [src]=\"item.data.thumb\">\n```\n\nNow every image element click will open the lightbox to the proper item\n\n**Full example:**\n\n```ts\nimport { Component, OnInit } from '@angular/core';\nimport { Gallery, GalleryItem } from '@ngx-gallery/core';\n\n@Component({\n  template: `\n    <img *ngFor=\"let item of items; index as i\"\n         [lightbox]=\"i\"\n         [src]=\"item.data.thumb\">\n  `\n})\nexport class AppComponent implements OnInit {\n\n  // Map the data to gallery image items\n  items: GalleryItem[] = data.map(item =>\n    new ImageItem({ src: item.srcUrl, thumb: item.previewUrl })\n  );\n\n  constructor(public gallery: Gallery) {\n  }\n\n  ngOnInit() {\n    // Load items into the gallery\n    this.gallery.ref().load(this.items);\n  }\n}\n```\n\nSee [Lightbox Example](https://murhafsousli.github.io/ngx-gallery/#/lightbox), [Lightbox Stackblitz](https://stackblitz.com/edit/ngx-gallery-lightbox)\n\n### Open the lightbox manually\n\n```ts\nimport { Lightbox } from '@ngx-gallery/lightbox';\n\nconstructor(public lightbox: Lightbox) {\n}\n\nopenLightbox(index: number) {\n  this.lightbox.open(index);\n}\n```\n\n\n### For fullscreen mode\n\nBy default fullscreen is obtained on small size screen (mobile) but you can set it as the default for all screen sizes\n\n**Example:**\n\n```ts\nGalleryModule,\nLightboxModule.withConfig({\n  panelClass: 'fullscreen'\n})\n```\n\nOr straight through the open function\n\n```ts\nopenLightbox() {\n  this.lightbox.open(0, 'lightboxId', {\n    panelClass: 'fullscreen'\n  });\n}\n```\nSee [Lightbox-API](https://github.com/MurhafSousli/ngx-gallery/wiki/Lightbox-API)\n\n***\n\nMoreover, There is a directive `[gallerize]` that loads the images from the template and opens the lightbox on thumbnails click, check [Galleries example](https://github.com/MurhafSousli/ngx-gallery/wiki/Gallerize-directive)\n\n\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/lightbox/Lightbox.stories.ts",
    "content": "import type { Meta, StoryObj } from '@storybook/angular';\nimport { applicationConfig, moduleMetadata } from '@storybook/angular';\nimport { provideAnimations } from '@angular/platform-browser/animations';\nimport { LightboxModule } from 'ng-gallery/lightbox';\nimport { getHDImages } from '../pixabay/pixabay.service';\nimport { LightboxExampleComponent } from './lightbox-example';\n\nimport 'hammerjs';\n\nconst meta: Meta<LightboxExampleComponent> = {\n  title: 'Documentations/Lightbox',\n  component: LightboxExampleComponent,\n  decorators: [\n    moduleMetadata({\n      imports: [LightboxModule],\n    }),\n    applicationConfig({\n      providers: [provideAnimations()],\n    }),\n  ],\n};\n\nexport default meta;\ntype Story = StoryObj<LightboxExampleComponent>;\n\nexport const LightboxExample: Story = {\n  parameters: {\n    docs: {\n      source: {\n        code: `\n          <div class=\"container\">\n            <img *ngFor=\"let item of items; index as i\"\n               class=\"grid-image\"\n               [src]=\"item.data.thumb\"\n               [lightbox]=\"i\"\n               [gallery]=\"galleryId\"/>\n          </div>\n        `,\n      },\n    },\n  },\n  render: (args: LightboxExampleComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n  }),\n  loaders: [async () => ({ items: await getHDImages('ship') })],\n};\n\nexport const GallerizeExample: Story = {\n  render: (args: LightboxExampleComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `\n      <div class=\"container\" gallerize>\n        <img *ngFor=\"let item of items\"\n             [src]=\"item.data.thumb\"\n             [attr.imageSrc]=\"item.data.src\">\n      </div>\n    `,\n    styles: [\n      `\n      .container {\n        display: flex;\n        flex-wrap: wrap;\n        gap: 3px;\n        margin: 0 auto;\n        padding: 20px;\n        max-width: 768px;\n      }\n      img {\n        height: 90px;\n        width: 125px;\n        object-fit: cover;\n      }\n    `,\n    ],\n  }),\n  loaders: [async () => ({ items: await getHDImages('ship') })],\n};\n\nexport const GallerizeGalleryExample: Story = {\n  render: (args: LightboxExampleComponent, { loaded: { items } }) => ({\n    props: { ...args, items },\n    template: `\n      <gallery [items]=\"items\" gallerize />\n    `,\n  }),\n  loaders: [async () => ({ items: await getHDImages('ship') })],\n};\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/lightbox/lightbox-example.ts",
    "content": "import { Component, Input, OnDestroy, OnInit, ChangeDetectionStrategy } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { Gallery, GalleryItem, GalleryRef } from 'ng-gallery';\nimport { Lightbox, LightboxModule } from 'ng-gallery/lightbox';\n\n@Component({\n  selector: 'lightbox-example',\n  template: `\n    <div class=\"container\">\n      <img *ngFor=\"let item of items; index as i\"\n         class=\"grid-image\"\n         [src]=\"item.data.thumb\"\n         [lightbox]=\"i\"\n         [gallery]=\"galleryId\"/>\n    </div>\n  `,\n  styles: [`\n    .container {\n      display: flex;\n      flex-wrap: wrap;\n      gap: 3px;\n      margin: 0 auto;\n      padding: 20px;\n      max-width: 768px;\n    }\n    img {\n      height: 90px;\n      width: 125px;\n      object-fit: cover;\n    }\n  `],\n  imports: [CommonModule, LightboxModule],\n  changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class LightboxExampleComponent implements OnInit, OnDestroy {\n\n  galleryId: string = 'lightbox';\n\n  galleryRef: GalleryRef;\n\n  @Input() items: GalleryItem[];\n\n  constructor(public gallery: Gallery, public lightbox: Lightbox) {\n  }\n\n  ngOnInit(): void {\n    this.galleryRef = this.gallery.ref('lightbox', {\n      thumbPosition: 'top',\n      imageSize: 'cover',\n      autoHeight: false\n    });\n\n    this.galleryRef.load(this.items);\n  }\n\n  ngOnDestroy(): void {\n    this.galleryRef.destroy();\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/pixabay/pixabay.model.ts",
    "content": "export interface PixabayModel {\n  total: number;\n  totalHits: number;\n  hits: Hit[];\n}\n\nexport interface Hit {\n  id: number;\n  pageURL: string;\n  type: string;\n  tags: string[];\n  previewURL: string;\n  previewWidth: number;\n  previewHeight: number;\n  webformatURL: string;\n  webformatWidth: number;\n  webformatHeight: number;\n  imageWidth: number;\n  imageHeight: number;\n  imageSize: number;\n  views: number;\n  downloads: number;\n  favorites: number;\n  likes: number;\n  comments: number;\n  user_id: number;\n  user: string;\n  userImageURL: string;\n}\nexport interface PixabayHDModel {\n  total: number;\n  totalHits: number;\n  hits: Hit2[];\n}\n\nexport interface Hit2 {\n  id_hash: string;\n  type: string;\n  previewURL: string;\n  previewWidth: number;\n  previewHeight: number;\n  webformatURL: string;\n  webformatWidth: number;\n  webformatHeight: number;\n  largeImageURL: string;\n  fullHDURL: string;\n  imageWidth: number;\n  imageHeight: number;\n  imageSize: number;\n  imageURL: string;\n  user_id: number;\n  user: string;\n  userImageURL: string;\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/stories/pixabay/pixabay.service.ts",
    "content": "import { ImageItem, GalleryItem } from 'ng-gallery';\nimport { Hit2, PixabayHDModel } from './pixabay.model';\n\nconst API_KEY: string = '560162-704dd2880c027f22c62ab7941';\n\nexport function getHDImages(key: string): Promise<GalleryItem[]> {\n  const url: string = `https://pixabay.com/api/?key=${ API_KEY }&q=${ encodeURIComponent(key) }&response_group=high_resolution&editors_choice=true&per_page=18&image_type=photo`;\n\n  return fetch(url).then((r: Response) => r.json()).then((data: PixabayHDModel) => {\n    return data.hits.map((item: Hit2, i: number) => {\n      return new ImageItem({ src: item.largeImageURL, thumb: item.previewURL, alt: `photo-${ i }` })\n    });\n  })\n}\n\nexport function getHDImagesForCustomTemplate(key: string): Promise<GalleryItem[]> {\n  const url: string = `https://pixabay.com/api/?key=${ API_KEY }&q=${ encodeURIComponent(key) }&response_group=high_resolution&editors_choice=true&per_page=18&image_type=photo`;\n\n  return fetch(url).then((r: Response) => r.json()).then((data: PixabayHDModel) => {\n    return data.hits.map((item: Hit2, i: number) => {\n      return {\n        type: 'custom',\n        data: {\n          src: item.largeImageURL,\n          thumb: item.previewURL, alt: `photo-${ i }`\n        }\n      }\n    });\n  })\n}\n"
  },
  {
    "path": "projects/ng-gallery/src/test.ts",
    "content": "// This file is required by karma.conf.js and loads recursively all the .spec and framework files\n\nimport 'zone.js';\nimport 'zone.js/testing';\nimport { getTestBed } from '@angular/core/testing';\nimport {\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting\n} from '@angular/platform-browser-dynamic/testing';\n\n// First, initialize the Angular testing environment.\ngetTestBed().initTestEnvironment(\n  BrowserDynamicTestingModule,\n  platformBrowserDynamicTesting(), {\n    teardown: { destroyAfterEach: false }\n  }\n);\n"
  },
  {
    "path": "projects/ng-gallery/tsconfig.lib.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../out-tsc/lib\",\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"inlineSources\": true,\n    \"types\": []\n  },\n  \"exclude\": [\n    \"**/*.spec.ts\"\n  ]\n}\n"
  },
  {
    "path": "projects/ng-gallery/tsconfig.lib.prod.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"./tsconfig.lib.json\",\n  \"compilerOptions\": {\n    \"declarationMap\": false\n  },\n  \"angularCompilerOptions\": {\n    \"compilationMode\": \"partial\"\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"include\": [\n    \"**/*.spec.ts\",\n    \"**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/public/icons/browserconfig.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n    <msapplication>\n        <tile>\n            <square150x150logo src=\"/mstile-150x150.png\"/>\n            <TileColor>#da532c</TileColor>\n        </tile>\n    </msapplication>\n</browserconfig>\n"
  },
  {
    "path": "projects/ng-gallery-demo/public/icons/site.webmanifest.json",
    "content": "{\n    \"name\": \"Angular Gallery\",\n    \"short_name\": \"Angular Gallery\",\n    \"icons\": [\n        {\n            \"src\": \"/android-chrome-192x192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\"\n        },\n        {\n            \"src\": \"/android-chrome-256x256.png\",\n            \"sizes\": \"256x256\",\n            \"type\": \"image/png\"\n        }\n    ],\n    \"theme_color\": \"#ffffff\",\n    \"background_color\": \"#ffffff\",\n    \"display\": \"standalone\"\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/app-routing.animations.ts",
    "content": "import { trigger, animate, transition, style, query } from '@angular/animations';\n\nexport const fadeAnimation = trigger('sideAnimation', [\n\n  transition('* => *', [\n\n    query(':enter', [\n        style({\n          opacity: 0\n        })\n      ],\n      {optional: true}\n    ),\n    query(':leave', animate('0.4s ease-in-out', style({\n        opacity: 0\n      })),\n      {optional: true}\n    ),\n    query(':enter', animate('0.4s ease-in-out', style({\n        opacity: 1\n      })),\n      {optional: true}\n    )\n  ])\n]);\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/app.component.html",
    "content": "<ng-progress ngProgressRouter ngProgressHttp/>\n\n<mat-sidenav-container>\n  <mat-sidenav #sidenav>\n    <div class=\"logo\">\n      <mat-icon svgIcon=\"logo\"/>\n    </div>\n    <h3>Ng-gallery</h3>\n    <app-menu [toolbar]=\"false\"/>\n  </mat-sidenav>\n\n  <div fxLayout=\"column\" class=\"main-wrapper\">\n\n    <!-- MAIN TOOLBAR -->\n\n    <mat-toolbar color=\"primary\">\n      <a fxHide fxShow.lt-sm mat-icon-button class=\"toggle-button\" (click)=\"sidenav.open()\">\n        <mat-icon fontIcon=\"menu\"/>\n      </a>\n\n      <a class=\"toolbar-logo\" routerLink=\".\">\n        <mat-icon svgIcon=\"logo\"/>\n        <h1>Ng-gallery</h1>\n      </a>\n\n      <span fxFlex></span>\n      <app-menu fxHide.lt-sm/>\n    </mat-toolbar>\n\n    <!-- ROUTER OUTLET -->\n\n    <main style=\"flex: 1\" [@sideAnimation]=\"getState(o)\">\n      <router-outlet #o=\"outlet\"/>\n    </main>\n  </div>\n\n</mat-sidenav-container>\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/app.component.scss",
    "content": "@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.main-wrapper {\n  height: 100%;\n  position: relative;\n  transition: all linear 0.3s;\n  display: flex;\n  flex-direction: column;\n  background-color: var(--bg);\n}\n\n.logo {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  padding: 1em;\n  margin: 1em 0;\n\n  mat-icon {\n    height: 84px;\n    width: 84px;\n  }\n}\n\nmain {\n  position: relative;\n  overflow: hidden;\n  box-shadow: 0 -3px 4px rgba(0, 0, 0, 0.2);\n}\n\n.toolbar-logo {\n  display: flex;\n  align-items: center;\n\n  a {\n    letter-spacing: 1px;\n  }\n\n  h1 {\n    color: white;\n    font-size: 16px;\n    text-align: center;\n  }\n\n  span {\n    flex: 1;\n  }\n\n  mat-icon {\n    width: 40px;\n    height: 40px;\n    margin-right: 0.5em;\n  }\n}\n\n.mat-toolbar {\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  z-index: 110;\n  height: var(--header-height);\n  box-shadow: 0 3px 5px -1px #0003, 0 6px 10px #00000024, 0 1px 18px #0000001f;\n}\n\n.toolbar-buttons {\n  display: flex;\n  align-items: center;\n  margin-bottom: 0.5em;\n}\n\n.mat-sidenav {\n  background-color: var(--primary-color);\n\n  h3, menu {\n    color: white;\n  }\n}\n\n.toggle-button {\n  width: 40px;\n  height: 40px;\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/app.component.ts",
    "content": "import { Component, ViewChild, OnInit, ChangeDetectionStrategy } from '@angular/core';\nimport { NavigationEnd, Router, RouterModule } from '@angular/router';\nimport { DomSanitizer } from '@angular/platform-browser';\nimport { MatToolbarModule } from '@angular/material/toolbar';\nimport { MatSidenav, MatSidenavModule } from '@angular/material/sidenav';\nimport { MatIconModule, MatIconRegistry } from '@angular/material/icon';\nimport { MatButtonModule } from '@angular/material/button';\nimport { NgProgressbar } from 'ngx-progressbar';\nimport { NgProgressRouter } from 'ngx-progressbar/router';\nimport { NgProgressHttp } from 'ngx-progressbar/http';\n\nimport { FaIconLibrary } from '@fortawesome/angular-fontawesome';\nimport { faGithub, faTwitter } from '@fortawesome/free-brands-svg-icons';\nimport { faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons';\nimport { tap, filter } from 'rxjs';\n\nimport { fadeAnimation } from './app-routing.animations';\nimport { MenuComponent } from './shared/menu/menu.component';\n\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html',\n  styleUrl: './app.component.scss',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  animations: [fadeAnimation],\n  imports: [\n    NgProgressbar,\n    NgProgressRouter,\n    NgProgressHttp,\n    RouterModule,\n    MenuComponent,\n    MatIconModule,\n    MatToolbarModule,\n    MatSidenavModule,\n    MatButtonModule\n  ]\n})\nexport class AppComponent implements OnInit {\n\n  title: string;\n  @ViewChild(MatSidenav) sideNav: MatSidenav;\n\n  constructor(private router: Router, private matIconRegistry: MatIconRegistry, library: FaIconLibrary, domSanitizer: DomSanitizer) {\n    library.addIcons(faTwitter, faGithub, faExternalLinkAlt);\n\n    this.matIconRegistry.addSvgIcon(\n      'logo',\n      domSanitizer.bypassSecurityTrustResourceUrl('img/ng-gallery.svg')\n    );\n  }\n\n  ngOnInit() {\n    /** When router changes */\n    this.router.events.pipe(\n      filter(event => event instanceof NavigationEnd),\n      tap((e) => {\n        this.sideNav.close()\n      })\n    ).subscribe();\n  }\n\n  getState(outlet) {\n    return outlet.isActivated ? outlet.activatedRoute : '';\n  }\n\n}\n\n\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/app.config.server.ts",
    "content": "import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';\nimport { provideServerRendering } from '@angular/platform-server';\nimport { appConfig } from './app.config';\n\nconst serverConfig: ApplicationConfig = {\n  providers: [\n    provideServerRendering()\n  ]\n};\n\nexport const config = mergeApplicationConfig(appConfig, serverConfig);\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/app.config.ts",
    "content": "import { ApplicationConfig } from '@angular/core';\nimport { provideRouter, withHashLocation } from '@angular/router';\nimport { provideClientHydration } from '@angular/platform-browser';\nimport { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';\nimport { provideAnimationsAsync } from '@angular/platform-browser/animations/async';\nimport { provideGalleryOptions } from 'ng-gallery';\nimport { provideHighlightOptions } from 'ngx-highlightjs';\nimport { progressInterceptor } from 'ngx-progressbar/http';\nimport { provideNgProgressRouter } from 'ngx-progressbar/router';\nimport { provideScrollbarOptions } from 'ngx-scrollbar';\n\nimport { appRoutes } from './app.routes';\n\nexport const appConfig: ApplicationConfig = {\n  providers: [\n    provideRouter(appRoutes, withHashLocation()),\n    provideClientHydration(),\n    provideAnimationsAsync(),\n    provideGalleryOptions({\n      imageSize: 'cover'\n    }),\n    provideHighlightOptions({\n      coreLibraryLoader: () => import('highlight.js/lib/core'),\n      languages: {\n        typescript: () => import('highlight.js/lib/languages/typescript'),\n        css: () => import('highlight.js/lib/languages/css'),\n        xml: () => import('highlight.js/lib/languages/xml')\n      }\n    }),\n    provideScrollbarOptions({\n      appearance: 'compact'\n    }),\n    provideNgProgressRouter({\n      minDuration: 600\n    }),\n    provideHttpClient(\n      withFetch(),\n      withInterceptors([progressInterceptor])\n    )\n  ]\n};\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/app.routes.ts",
    "content": "import { Routes } from '@angular/router';\n\nexport const appRoutes: Routes = [\n  {\n    path: '',\n    loadComponent: () => import('./pages/home/home.component').then(m => m.HomeComponent),\n    pathMatch: 'full'\n  },\n  {\n    path: 'getting-started',\n    loadChildren: () => import('./pages/documentation/routes').then(m => m.DOCUMENTATION_ROUTES)\n  },\n  {\n    path: 'gallery',\n    loadComponent: () => import('./pages/gallery-example/gallery-example.component').then(m => m.GalleryExampleComponent)\n  },\n  {\n    path: 'lightbox',\n    loadComponent: () => import('./pages/lightbox-example/lightbox-example.component').then(m => m.LightboxExampleComponent)\n  },\n  {\n    path: 'gallerize',\n    loadComponent: () => import('./pages/gallerize-example/gallerize-example.component').then(m => m.GallerizeExampleComponent)\n  },\n  {\n    path: 'custom-templates',\n    loadComponent: () => import('./pages/templates-example/templates-example.component').then(m => m.TemplatesExampleComponent)\n  },\n  {\n    path: 'lab',\n    loadComponent: () => import('./pages/lab/lab.component').then(m => m.LabComponent)\n  },\n  {\n    path: '**',\n    loadComponent: () => import('./pages/not-found/not-found.component').then(m => m.NotFoundComponent)\n  }\n];\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/doc-core/doc-core.component.html",
    "content": "<section>\n  <section-title>Installation</section-title>\n  <p>Install with NPM</p>\n\n  <div class=\"install\">\n    <b>NPM</b>\n    <hl-code code=\"npm i ng-gallery @angular/cdk\" disabled/>\n  </div>\n</section>\n\n<section>\n  <section-title>Usage</section-title>\n\n  <p>Since the standalone components has been the new standard in Angular >= 15, you can import <code class=\"hljs\">GalleryModule</code> in your component imports.</p>\n  <p>See <a mat-button class=\"inline-button\" style=\"padding: 0 0.5em\" color=\"accent\" routerLink=\"/gallery\">Gallery\n    Example</a> and <a mat-button class=\"inline-button\" style=\"padding: 0 0.5em\" color=\"accent\"\n                       routerLink=\"/custom-templates\">Custom Templates Example</a>\n  </p>\n\n  <p></p>\n  <p></p>\n\n  <note>\n    <p>To set global config that applies on all the galleries across the app, provide the config value using <code\n      class=\"hljs\">GALLERY_CONFIG</code> token.</p>\n  </note>\n\n  <hl-code [code]=\"globalConfig\"/>\n\n</section>\n\n<section>\n  <section-title>Mouse Sliding (optional)</section-title>\n\n  <p><b>ng-gallery</b> relies on HammerJS to support dragging the slider using the mouse, if you don't want this feature you can skip installing HammerJS.</p>\n\n  <p class=\"note\">You can add HammerJS to your application via <a\n    href=\"https://www.npmjs.com/package/hammerjs\">npm</a>, a CDN (such as the <a\n    href=\"https://developers.google.com/speed/libraries/#hammerjs\">Google CDN</a>), or served directly from your\n    app.\n  </p>\n\n  <div class=\"install\">\n    <b>NPM</b>\n    <hl-code code=\"npm i hammerjs\" disabled/>\n  </div>\n\n  <p>After installing, import HammerJS into <code class=\"hljs\">src/polyfills.ts</code></p>\n\n  <hl-code code=\"import 'hammerjs';\"/>\n\n</section>\n\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/doc-core/doc-core.component.scss",
    "content": ""
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/doc-core/doc-core.component.ts",
    "content": "import { ChangeDetectionStrategy, Component } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport { MatButtonModule } from '@angular/material/button';\nimport { NoteComponent } from '../../../shared/note/note.component';\nimport { HlCodeComponent } from '../../../shared/hl-code/hl-code.component';\nimport { SectionTitleComponent } from '../../../shared/section-title/section-title.component';\n\n@Component({\n  selector: 'app-getting-started',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  templateUrl: './doc-core.component.html',\n  styleUrl: './doc-core.component.scss',\n  imports: [SectionTitleComponent, HlCodeComponent, MatButtonModule, RouterLink, NoteComponent]\n})\nexport class DocCoreComponent {\n\n  readonly globalConfig: string = `import { GALLERY_CONFIG, GalleryConfig } from 'ng-gallery';\n\nbootstrapApplication(AppComponent, {\n  providers: [\n    {\n      provide: GALLERY_CONFIG,\n      useValue: {\n        autoHeight: true,\n        imageSize: 'cover'\n      } as GalleryConfig\n    }\n  ]\n})`\n\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/doc-lightbox/doc-lightbox.component.html",
    "content": "<section>\n  <section-title>Installation</section-title>\n  <p>Install with NPM</p>\n\n  <div class=\"install\">\n    <b>NPM</b>\n    <hl-code code=\"npm i ng-gallery @angular/cdk\" disabled/>\n  </div>\n</section>\n\n<section>\n  <section-title>Usage</section-title>\n\n  <div class=\"usage-title\">\n    <span class=\"section-number\">1</span>\n    <p>Import <code highlightAuto=\"@use '~@angular/cdk/overlay-prebuilt.css';\" class=\"hljs\"></code> in the global styles</p>\n  </div>\n\n  <p></p>\n  <p></p>\n\n  <div class=\"usage-title\">\n    <span class=\"section-number\">2</span>\n    <p>See <a mat-button class=\"inline-button\" style=\"padding: 0 0.5em\" color=\"accent\" routerLink=\"/lightbox\">Lightbox\n      Example</a> and <a mat-button class=\"inline-button\" style=\"padding: 0 0.5em\" color=\"accent\" routerLink=\"/gallerize\">Gallerize\n      Example</a>\n    </p>\n  </div>\n\n  <note>\n    <p>To set global config that applies on all the lightbox overlays across the app, provide the config value using\n      <code class=\"hljs\">LIGHTBOX_CONFIG</code> token.</p>\n  </note>\n\n  <hl-code [code]=\"globalConfig\"/>\n\n</section>\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/doc-lightbox/doc-lightbox.component.scss",
    "content": ""
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/doc-lightbox/doc-lightbox.component.ts",
    "content": "import { ChangeDetectionStrategy, Component } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport { MatButtonModule } from '@angular/material/button';\nimport { NoteComponent } from '../../../shared/note/note.component';\nimport { HlCodeComponent } from '../../../shared/hl-code/hl-code.component';\nimport { SectionTitleComponent } from '../../../shared/section-title/section-title.component';\nimport { HighlightAuto } from 'ngx-highlightjs';\n\n@Component({\n  selector: 'app-doc-lightbox',\n  templateUrl: './doc-lightbox.component.html',\n  styleUrls: ['./doc-lightbox.component.scss'],\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  imports: [SectionTitleComponent, HlCodeComponent, MatButtonModule, RouterLink, NoteComponent, HighlightAuto]\n})\nexport class DocLightboxComponent {\n\n  readonly globalConfig: string = `import { LIGHTBOX_CONFIG, LightboxConfig } from 'ng-gallery/lightbox';\n\nbootstrapApplication(AppComponent, {\n  providers: [\n    {\n      provide: LIGHTBOX_CONFIG,\n      useValue: {\n        keyboardShortcuts: false,\n        exitAnimationTime: 1000\n      } as LightboxConfig\n    }\n  ]\n})`;\n\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/documentation.component.html",
    "content": "<div class=\"page-title\">\n  <h1>Getting Started</h1>\n</div>\n\n<div class=\"page-content\">\n\n  <div fxLayout fxLayout.lt-md=\"column\" fxLayoutGap=\"15px\">\n    <div class=\"doc-menu-container\">\n      <ul class=\"doc-menu\">\n        <li><a routerLink=\"./gallery\" routerLinkActive=\"active-link\">Gallery installation</a></li>\n        <li><a routerLink=\"./lightbox\" routerLinkActive=\"active-link\">Lightbox installation</a></li>\n      </ul>\n    </div>\n\n    <div style=\"width: 100%\">\n      <section>\n        <section-title>Overview</section-title>\n\n        <div class=\"usage-title\">\n            <span class=\"section-number\">\n              <mat-icon>star</mat-icon>\n            </span>\n          <p>This library consists of 3 packages:</p>\n        </div>\n\n        <div fxLayout>\n          <div fxHide.lt-md=\"true\" fxLayout=\"column\" fxLayoutAlign=\"center center\" style=\"width: 30px\">\n            <div class=\"separator\"></div>\n          </div>\n          <div fxFlex class=\"intro\">\n            <p><b>ng-gallery</b> provides <code class=\"hljs\" [textContent]=\"'<gallery>'\"></code> component and <code\n              class=\"hljs\">Gallery</code> service.</p>\n            <p><b>ng-gallery/lightbox</b> provides <code class=\"hljs\">Lightbox</code> service and <code class=\"hljs\">[lightbox]</code>\n              directive that opens the gallery in a modal.</p>\n            <p>The package provides <code class=\"hljs\">[gallerize]</code> directive that automatically adds images to\n              the lightbox.</p>\n          </div>\n        </div>\n      </section>\n\n      <router-outlet></router-outlet>\n    </div>\n  </div>\n</div>\n<footer></footer>\n\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/documentation.component.scss",
    "content": ".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  top: 3em;\n  width: 200px;\n  height: 150px;\n  list-style-type: none;\n  padding: 0;\n\n  li {\n    margin-bottom: 1em;\n  }\n}\n\n.active-link {\n  text-decoration: underline;\n}\n\n.note {\n  margin: 2em 0;\n  font-style: italic;\n  color: gray;\n  font-size: 13px\n}\n\nsection {\n  border-top: 0;\n  margin-top: 0;\n}\n\n.intro {\n  margin-left: 1.6em;\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/documentation.component.ts",
    "content": "import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';\nimport { Title } from '@angular/platform-browser';\nimport { RouterModule } from '@angular/router';\nimport { MatIconModule } from '@angular/material/icon';\nimport { FooterComponent } from '../../shared/footer/footer.component';\nimport { SectionTitleComponent } from '../../shared/section-title/section-title.component';\n\n@Component({\n  host: {\n    'class': 'page'\n  },\n  selector: 'documentation',\n  templateUrl: './documentation.component.html',\n  styleUrls: ['./documentation.component.scss'],\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  imports: [\n    RouterModule,\n    SectionTitleComponent,\n    MatIconModule,\n    FooterComponent\n  ]\n})\nexport class DocumentationComponent implements OnInit {\n\n  constructor(private _title: Title) {\n  }\n\n  ngOnInit() {\n    this._title.setTitle('Getting Started | ng-gallery');\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/documentation/routes.ts",
    "content": "import { Routes } from '@angular/router';\nimport { DocumentationComponent } from './documentation.component';\n\nexport const DOCUMENTATION_ROUTES: Routes = [\n  {\n    path: '',\n    component: DocumentationComponent,\n    children: [\n      {\n        path: '',\n        redirectTo: 'gallery',\n        pathMatch: 'full'\n      },\n      {\n        path: 'gallery',\n        loadComponent: () => import('./doc-core/doc-core.component').then(m => m.DocCoreComponent)\n      },\n      {\n        path: 'lightbox',\n        loadComponent: () => import('./doc-lightbox/doc-lightbox.component').then(m => m.DocLightboxComponent)\n      }\n    ]\n  }\n];\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/examples/basic-example/basic-example.ts",
    "content": "import { ChangeDetectionStrategy, Component } from '@angular/core';\nimport { GalleryComponent, GalleryItemDef } from 'ng-gallery';\n\n@Component({\n  selector: 'basic-example',\n  template: `\n    <gallery [items]=\"items\" [style.color]=\"'white'\">\n      <div *galleryItemDef=\"let item\">\n        Test {{ item?.text }}\n      </div>\n    </gallery>\n  `,\n  imports: [GalleryComponent, GalleryItemDef],\n  changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class BasicExampleComponent {\n  items: any[] = [\n    {\n      text: 'Hello'\n    },\n    {\n      text: 'Hello'\n    }\n  ]\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/gallerize-example/gallerize-example.component.html",
    "content": "<div class=\"page-title\">\n  <h1>Gallerize Directive</h1>\n</div>\n<div class=\"page-content\">\n  <section>\n    <section-title>Overview</section-title>\n    <p>The <code class=\"hljs\">gallerize</code> directive is used to create lightbox gallery from image elements or\n      gallery components.</p>\n\n    <p>This directive loads the images in the gallery automatically, you don't need to load them manually.</p>\n\n    <p><b>Gallerize</b> has 2 modes:</p>\n\n    <ul>\n      <li>\n          <span>If used on <code class=\"hljs\">HTMLElement</code>, it detects the images and hooks their clicks to the\n            lightbox.</span>\n      </li>\n      <li>\n          <span>If used on <code class=\"hljs\" [textContent]=\"'<gallery>'\"></code>, it hooks the images click to the\n            lightbox.</span>\n      </li>\n    </ul>\n\n    <note class=\"info\" style=\"margin-top: -1em\">\n      <p>If you haven't installed the module in step <span class=\"section-number inline-number\">1</span>\n        see the <a mat-button class=\"inline-button\" style=\"padding: 0 0.5em\" color=\"accent\"\n                   [routerLink]=\"['/getting-started/lightbox']\">Getting\n          Started Guide</a></p>\n    </note>\n\n  </section>\n\n  <section>\n    <section-title>Auto-detect images</section-title>\n\n    <p>There are use cases when images are received as HTML format, this directive takes the\n      headache of fetching the src paths for you and connect them automatically to the lightbox.</p>\n\n    <h3>Basic example</h3>\n\n    <hl-code [code]=\"code.onElements\"/>\n\n    <div class=\"row\" gallerize=\"auto-detect\">\n      <div class=\"column\">\n        <img *ngFor=\"let item of examples$ | async | slice:0:3\"\n             [src]=\"item.data.thumb\"\n             [attr.imageSrc]=\"item.data.src\"\n             [attr.thumbSrc]=\"item.data.thumb\">\n      </div>\n      <div class=\"column\">\n        <img *ngFor=\"let item of examples$ | async | slice:3:6\"\n             [src]=\"item.data.thumb\"\n             [attr.imageSrc]=\"item.data.src\"\n             [attr.thumbSrc]=\"item.data.thumb\">\n      </div>\n      <div class=\"column\">\n        <img *ngFor=\"let item of examples$ | async | slice:6:9\"\n             [src]=\"item.data.thumb\"\n             [attr.imageSrc]=\"item.data.src\"\n             [attr.thumbSrc]=\"item.data.thumb\">\n      </div>\n    </div>\n\n    <note>\n      <p>By default, <b>Gallerize</b> will process all img elements, but you can limit that to elements with a\n        specific selector.</p>\n    </note>\n\n    <h3>Selector example</h3>\n\n    <p>Detect background images of elements with selector <code class=\"hljs\">.gallery-img</code></p>\n\n    <hl-code [code]=\"code.withSelector\"/>\n\n  </section>\n\n  <section>\n    <section-title>Use with <code class=\"hljs\" [textContent]=\"'<gallery>'\"></code> Component</section-title>\n    <p>Gallerize is also compatible with the <code class=\"hljs\" [textContent]=\"'<gallery>'\"></code> component, if you\n      want to trigger the lightbox on a gallery item's click, just add <code class=\"hljs\">gallerize</code> on your\n      gallery component.\n    </p>\n\n    <gallery id=\"gallerize-ex\" thumbs gallerize [items]=\"images$ | async\"/>\n\n    <h3>Code</h3>\n\n    <hl-code [code]=\"code.onGallery\"/>\n  </section>\n</div>\n\n<footer></footer>\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/gallerize-example/gallerize-example.component.scss",
    "content": "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      cursor: pointer;\n    }\n\n    gallery-thumb {\n      padding: 0;\n    }\n  }\n}\n\nul {\n  list-style: none\n}\n\nli {\n  display: flex;\n  align-items: center;\n\n  &:before {\n    margin-right: 10px;\n    content: \"•\";\n    color: var(--accent-color);\n    font-size: 2em;\n  }\n}\n\n.column {\n  max-width: 150px;\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/gallerize-example/gallerize-example.component.ts",
    "content": "import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';\nimport { NgFor, AsyncPipe, SlicePipe } from '@angular/common';\nimport { Title } from '@angular/platform-browser';\nimport { RouterLink } from '@angular/router';\nimport { MatButtonModule } from '@angular/material/button';\nimport { GalleryComponent } from 'ng-gallery';\nimport { GallerizeDirective } from 'ng-gallery/lightbox';\nimport { Pixabay } from '../../service/pixabay.service';\nimport { FooterComponent } from '../../shared/footer/footer.component';\nimport { HlCodeComponent } from '../../shared/hl-code/hl-code.component';\nimport { NoteComponent } from '../../shared/note/note.component';\nimport { SectionTitleComponent } from '../../shared/section-title/section-title.component';\n\n@Component({\n  host: {\n    'class': 'page'\n  },\n  selector: 'gallerize-example',\n  templateUrl: './gallerize-example.component.html',\n  styleUrls: ['./gallerize-example.component.scss'],\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  imports: [SectionTitleComponent, NoteComponent, MatButtonModule, RouterLink, HlCodeComponent, GallerizeDirective, NgFor, GalleryComponent, FooterComponent, AsyncPipe, SlicePipe]\n})\nexport class GallerizeExampleComponent implements OnInit {\n\n  readonly code: any;\n  readonly images$ = this._pixabay.getHDImages('jaguar');\n  readonly examples$ = this._pixabay.getHDImages('crocodile');\n\n  constructor(private _pixabay: Pixabay, private _title: Title) {\n    this.code = code;\n    // gallery.ref('lightbox').setConfig({\n    //   // thumbPosition: 'bottom',\n    //   imageSize: 'cover',\n    //   itemAutosize: false,\n    //   // thumbAutosize: false,\n    //   // thumbs: true\n    //   // thumbWidth: 120,\n    //   // thumbHeight: 90,\n    //   // thumbView: 'contain'\n    // });\n    // gallery.ref('auto-detect').setConfig({\n    //   // thumbPosition: 'top',\n    //   imageSize: 'cover',\n    //   itemAutosize: false,\n    //   // thumbAutosize: false,\n    //   // thumbs: true\n    //   // thumbView: 'contain'\n    // });\n  }\n\n  ngOnInit() {\n    this._title.setTitle('Gallerize | ng-gallery');\n  }\n\n}\n\nconst code = {\n  onElements: `<div class=\"container\" gallerize>\n  <img *ngFor=\"let image of images\"\n       [src]=\"image.thumb\"\n       [attr.imageSrc]=\"image.src\"\n       [attr.thumbSrc]=\"image.thumb\"/>\n</div>`,\n  withSelector: `<div class=\"container\" gallerize selector=\".gallery-img\">\n  <div *ngFor=\"let image of images\" class=\"gallery-img\"\n       [style.backgroundImage]=\"'url(' + image.thumb + ')'\"\n       [attr.imageSrc]=\"image.src\"\n       [attr.thumbSrc]=\"image.thumb\"></div>\n</div>`,\n  onGallery: `<gallery thumbs gallerize [items]=\"cameraImages\"></gallery>`\n};\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/gallery-example/gallery-example.component.html",
    "content": "<div class=\"page-title\">\n  <h1>Gallery Component</h1>\n</div>\n<div class=\"page-content\">\n\n  <section>\n    <section-title>Overview</section-title>\n    <p><code class=\"hljs\" [textContent]=\"'<gallery>'\"></code> a slideshow component for cycling through\n      elements - images, videos or iframes - like a carousel.</p>\n  </section>\n\n  <section>\n    <section-title class=\"no-margin-bottom\">Usage</section-title>\n\n    <note class=\"info\" style=\"margin: -3em 0 -1em\">\n      <p>If you haven't installed the module in step <span class=\"section-number inline-number\">1</span>\n        see the <a mat-button\n                   class=\"inline-button\"\n                   style=\"padding: 0 0.5em\"\n                   color=\"accent\"\n                   routerLink=\"/getting-started/gallery\">\n          Getting Started Guide\n        </a></p>\n    </note>\n\n    <div class=\"usage-title\">\n      <span class=\"section-number\">2</span>\n      <p>Create a gallery items array to load it in the gallery</p>\n    </div>\n\n    <mat-tab-group>\n\n      <mat-tab label=\"Basic\">\n        <ng-template matTabContent>\n          <p>The most basic usage is to load items using an array of type <code class=\"hljs\">GalleryItem[]</code></p>\n          <p>This is done using the template binding</p>\n          <hl-code [code]=\"code.basic\"/>\n        </ng-template>\n      </mat-tab>\n\n      <mat-tab label=\"GalleryComponent\">\n        <ng-template matTabContent>\n          <p>Alternatively, The gallery can be accessed using the component reference.</p>\n          <p>Get the component using <code class=\"hljs\">&#64;ViewChild(GalleryComponent)</code> then add items</p>\n          <hl-code [code]=\"code.galleryCmp\"/>\n        </ng-template>\n      </mat-tab>\n\n      <mat-tab label=\"GalleryRef\">\n        <ng-template matTabContent>\n          <p>Access the gallery from anywhere in your app.</p>\n          <p>Use the <code class=\"hljs\">Gallery</code> service to get the <code class=\"hljs\">GalleryRef</code> by id\n          </p>\n          <hl-code [code]=\"code.galleryRef\"/>\n        </ng-template>\n      </mat-tab>\n\n    </mat-tab-group>\n\n    <h3>Example</h3>\n\n    @if (media$ | async; as config) {\n      <gallery id=\"basic\"\n               [items]=\"fruits$ | async\"\n               bullets>\n        <gallery-thumbs [thumbWidth]=\"config.thumbWidth\"\n                        [thumbHeight]=\"config.thumbHeight\"\n                        [position]=\"config.thumbPosition\"/>\n      </gallery>\n    }\n\n    <hl-code [code]=\"code.example\"/>\n\n    <note>\n      <p>The gallery has many options that you can adjust, check all available options in\n        <a mat-button class=\"inline-button\" color=\"primary\" target=\"_blank\"\n           href=\"https://github.com/MurhafSousli/ngx-gallery/wiki/Gallery-API\">\n          <fa-icon icon=\"external-link-alt\"/>\n          Gallery API\n        </a>\n      </p>\n    </note>\n\n    <p>Beside these options, you can also use CSS to change gallery's style.</p>\n\n    <p>For more advanced usage, check the\n      <a mat-button class=\"inline-button\" style=\"padding: 0 0.5em\" color=\"accent\" routerLink=\"/custom-templates\">Custom\n        Templates\n        Example</a>\n    </p>\n\n  </section>\n\n</div>\n<footer></footer>\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/gallery-example/gallery-example.component.scss",
    "content": "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;\n}\n\n.mat-chip {\n  margin-right: 1em;\n}\n\n.usage-title {\n  margin-bottom: 2em;\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/gallery-example/gallery-example.component.ts",
    "content": "import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';\nimport { AsyncPipe } from '@angular/common';\nimport { RouterLink } from '@angular/router';\nimport { Title } from '@angular/platform-browser';\nimport { MatTabsModule } from '@angular/material/tabs';\nimport { MatButtonModule } from '@angular/material/button';\nimport { GalleryModule, GalleryItemData, GalleryConfig, THUMB_POSITION, GalleryThumbsComponent } from 'ng-gallery';\nimport { FontAwesomeModule } from '@fortawesome/angular-fontawesome';\nimport { Observable, map } from 'rxjs';\n\nimport { Pixabay } from '../../service/pixabay.service';\nimport { FooterComponent } from '../../shared/footer/footer.component';\nimport { HlCodeComponent } from '../../shared/hl-code/hl-code.component';\nimport { NoteComponent } from '../../shared/note/note.component';\nimport { SectionTitleComponent } from '../../shared/section-title/section-title.component';\n\n@Component({\n  host: {\n    'class': 'page'\n  },\n  selector: 'gallery-example',\n  templateUrl: './gallery-example.component.html',\n  styleUrl: './gallery-example.component.scss',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  imports: [\n    SectionTitleComponent,\n    NoteComponent,\n    MatButtonModule,\n    RouterLink,\n    MatTabsModule,\n    HlCodeComponent,\n    GalleryModule,\n    FontAwesomeModule,\n    FooterComponent,\n    AsyncPipe,\n    GalleryThumbsComponent\n  ]\n})\nexport class GalleryExampleComponent implements OnInit {\n\n  readonly code = code;\n  readonly fruits$: Observable<GalleryItemData[]>;\n  readonly media$: Observable<any>;\n\n  constructor(pixabay: Pixabay, private _title: Title) {\n    this.fruits$ = pixabay.getHDImages('vegetables');\n    // this.media$ = mediaObserver.asObservable().pipe(\n    //   map((res: MediaChange[]) => {\n    //     if (res.some((x => x.mqAlias === 'sm' || x.mqAlias === 'xs'))) {\n    //       return {\n    //         thumbPosition: ThumbnailsPosition.Top,\n    //         thumbWidth: 80,\n    //         thumbHeight: 80\n    //       };\n    //     }\n    //     return {\n    //       thumbPosition: ThumbnailsPosition.Left,\n    //       thumbWidth: 120,\n    //       thumbHeight: 90\n    //     };\n    //   })\n    // );\n  }\n\n  ngOnInit() {\n    this._title.setTitle('Gallery | ng-gallery');\n  }\n\n}\n\nconst code = {\n  example: '<gallery [items]=\"items\" thumbPosition=\"left\"></gallery>',\n  basic: `import { Component, OnInit } from '@angular/core';\nimport { GalleryModule, GalleryItemData, ImageItem } from 'ng-gallery';\n\n@Component({\n  template: \\`\n    <gallery [items]=\"images\"></gallery>\n  \\`,\n  imports: [GalleryModule]\n})\nexport class AppComponent implements OnInit {\n\n  images: GalleryItemData[];\n\n  ngOnInit() {\n    // Set items array\n    this.images = [\n      new ImageItem({ src: 'IMAGE_SRC_URL', thumb: 'IMAGE_THUMBNAIL_URL' })),\n      // ... more items\n    ];\n  }\n}`,\n  galleryCmp: `import { Component, OnInit } from '@angular/core';\nimport { GalleryModule, GalleryComponent, ImageItem } from 'ng-gallery';\n\n@Component({\n  template: \\`\n    <gallery></gallery>\n  \\`,\n  imports: [GalleryModule]\n})\nexport class AppComponent implements OnInit {\n\n  @ViewChild(GalleryComponent) gallery: GalleryComponent;\n\n  ngOnInit() {\n    // Add items individually\n    this.gallery.addImage({ src: 'IMAGE_SRC_URL', thumb: 'IMAGE_THUMBNAIL_URL' });\n\n    // Or load a new set of items\n    this.gallery.load([\n      new ImageItem({ src: 'IMAGE_SRC_URL', thumb: 'IMAGE_THUMBNAIL_URL' }),\n      // ... more items\n    ]);\n  }\n}`,\n  galleryRef: `import { Component, OnInit } from '@angular/core';\nimport { GalleryModule, Gallery, GalleryRef, ImageItem } from 'ng-gallery';\n\n@Component({\n  template: \\`\n    <gallery id=\"myGallery\"></gallery>\n  \\`,\n  imports: [GalleryModule]\n})\nexport class AppComponent implements OnInit {\n\n  constructor(private gallery: Gallery){\n  }\n\n  ngOnInit() {\n    // Get the galleryRef by id\n    const galleryRef = gallery.ref('myGallery');\n\n    // Add items individually\n    this.galleryRef.addImage({ src: 'IMAGE_SRC_URL', thumb: 'IMAGE_THUMBNAIL_URL' });\n\n    // Or load a new set of items\n    this.galleryRef.load([\n      new ImageItem({ src: 'IMAGE_SRC_URL', thumb: 'IMAGE_THUMBNAIL_URL' })\n      // ... more items\n    ]);\n  }\n}`\n};\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/home/home.component.html",
    "content": "<div style=\"height: 100%; overflow: auto\">\n  <div class=\"page-title\">\n    <h1>Ng-gallery</h1>\n    <h2>Simplifies the process of creating beautiful galleries.</h2>\n    <a mat-raised-button routerLink=\"/getting-started\">Get started</a>\n    <badges></badges>\n  </div>\n  <div class=\"page-content\">\n    @if (media$ | async; as config) {\n      <gallery id=\"basic\"\n               [items]=\"camel$ | async\"\n               ngStyle.lg=\"height: 540px\"\n               itemAutosize=\"true\"\n               imageSize=\"contain\"\n               loop\n               autoplay\n               debug>\n        <img *galleryItemDef=\"let item\"\n             galleryImage\n             [src]=\"item.src\"\n             fill\n             [attr.alt]=\"item.alt\"/>\n        <gallery-thumbs position=\"top\"\n                        centralized\n                        [thumbWidth]=\"config.thumbWidth\"\n                        [thumbHeight]=\"config.thumbHeight\"/>\n      </gallery>\n    }\n  </div>\n  <footer></footer>\n</div>\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/home/home.component.scss",
    "content": "@use \"../../../mixin\";\n\ngallery {\n  height: 700px;\n}\n\n.mat-mdc-raised-button:not(:disabled) {\n  font-weight: 500;\n  letter-spacing: 1px;\n}\n\n.logo {\n  display: flex;\n  align-items: center;\n  margin-top: 5em;\n  margin-bottom: 3em;\n\n  img {\n    margin-right: 0.5em;\n    width: 100px;\n  }\n}\n\nsection {\n  margin: 2em 0 3em;\n}\n\n.row {\n  margin-top: 2em;\n}\n\n.description {\n  font-size: 1.2em;\n}\n\n.showcase {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n}\n\n.showcase-item {\n  flex: 1;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n}\n\n.item-text {\n  flex: 1;\n  display: flex;\n  align-items: center;\n  opacity: 0.6;\n}\n\n.page-title {\n  height: 420px;\n  align-items: center;\n\n  h1 {\n    padding: 0;\n    font-size: 56px;\n    margin: 115px 5px 0;\n    font-weight: 700;\n  }\n\n  h2 {\n    text-align: center;\n    margin: 10px 0 20px;\n    font-size: 20px;\n    font-weight: 300;\n    line-height: 28px;\n  }\n\n  a {\n    margin: 30px 0 0;\n  }\n\n  badges {\n    margin: 30px 0;\n  }\n}\n\n.page-content {\n  padding: 0;\n  max-width: unset;\n}\n\n.black-bg {\n  background: var(--primary-color);\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/home/home.component.ts",
    "content": "import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';\nimport { NgIf, AsyncPipe } from '@angular/common';\nimport { Title } from '@angular/platform-browser';\nimport { RouterLink } from '@angular/router';\nimport { MatButtonModule } from '@angular/material/button';\n// import { FlexLayoutModule, MediaChange, MediaObserver } from '@angular/flex-layout';\nimport {\n  GalleryComponent,\n  GalleryConfig,\n  GalleryItemData,\n  GalleryItemDef,\n  GalleryThumbsComponent,\n  ImgRecognizer\n} from 'ng-gallery';\nimport { Observable, map } from 'rxjs';\nimport { Pixabay } from '../../service/pixabay.service';\nimport { FooterComponent } from '../../shared/footer/footer.component';\nimport { BadgesComponent } from '../../shared/badges/badges.component';\n\n@Component({\n  host: {\n    'class': 'page'\n  },\n  selector: 'home',\n  templateUrl: './home.component.html',\n  styleUrl: './home.component.scss',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  imports: [\n    MatButtonModule,\n    RouterLink,\n    BadgesComponent,\n    GalleryComponent,\n    FooterComponent,\n    AsyncPipe,\n    // FlexLayoutModule,\n    GalleryThumbsComponent,\n    GalleryItemDef,\n    ImgRecognizer\n  ]\n})\nexport class HomeComponent implements OnInit {\n\n  readonly camel$: Observable<GalleryItemData[]>;\n  readonly media$: Observable<any>;\n\n  constructor(pixabay: Pixabay, private _title: Title) {\n    this.camel$ = pixabay.getHDImages('mountain');\n    // this.media$ = mediaObserver.asObservable().pipe(\n    //   map((res: MediaChange[]) => {\n    //     if (res.some((x => x.mqAlias === 'sm' || x.mqAlias === 'xs'))) {\n    //       return {\n    //         thumbWidth: 80,\n    //         thumbHeight: 80\n    //       };\n    //     }\n    //     return {\n    //       thumbWidth: 120,\n    //       thumbHeight: 90\n    //     };\n    //   })\n    // );\n  }\n\n  ngOnInit() {\n    this._title.setTitle('Home | ng-gallery');\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/lab/lab.component.html",
    "content": "<div class=\"page-title\">\n  <h1 fxLayout>\n    <mat-icon style=\"font-size: 56px; height: 56px; width: 56px;\">biotech</mat-icon>\n    <div fxLayout fxFlexAlign=\"center\">Lab</div>\n  </h1>\n</div>\n<div class=\"page-content\">\n<!--  <basic-example/>-->\n\n  @if (show()) {\n    <div class=\"gallery-container\">\n      <gallery #gallery id=\"lab\"\n               [dir]=\"dir\"\n               [items]=\"photos()\"\n               [centralized]=\"config.centralized\"\n               [itemAutosize]=\"config.itemAutosize\"\n               [resizeDebounceTime]=\"config.resizeDebounceTime\"\n               [loop]=\"config.loop\"\n               [debug]=\"config.debug\"\n               [autoplay]=\"config.autoplay\"\n               [imageSize]=\"config.imageSize\"\n               [autoplayInterval]=\"config.autoplayInterval\"\n               [orientation]=\"config.orientation\"\n               [scrollDuration]=\"config.scrollDuration\"\n               [disableScroll]=\"config.disableScroll\"\n               [disableMouseScroll]=\"config.disableMouseScroll\"\n               [scrollBehavior]=\"config.scrollBehavior\"\n               (itemClick)=\"onItemClick($event)\"\n               (thumbClick)=\"onThumbClick($event)\">\n\n        <img *galleryItemDef=\"let item\"\n             galleryImage\n             [src]=\"item.src\"\n             fill\n             [attr.alt]=\"item.alt\"/>\n\n        @if (navConfig.nav) {\n          <gallery-nav/>\n        }\n\n        @if (bulletConfig.bullet) {\n          <gallery-bullets [align]=\"bulletConfig.align\"\n                           [scrollBehavior]=\"bulletConfig.scrollBehavior\"\n                           [disabled]=\"bulletConfig.disabled\"/>\n        }\n\n        @if (counterConfig.counter) {\n          <gallery-counter [align]=\"counterConfig.align\"/>\n        }\n\n        @if (thumbConfig.thumbs) {\n          <gallery-thumbs [autosize]=\"thumbConfig.autosize\"\n                          [imageSize]=\"thumbConfig.imageSize\"\n                          [centralized]=\"thumbConfig.centralized\"\n                          [thumbWidth]=\"thumbConfig.thumbWidth\"\n                          [thumbHeight]=\"thumbConfig.thumbHeight\"\n                          [disabled]=\"thumbConfig.disabled\"\n                          [disableScroll]=\"thumbConfig.disableScroll\"\n                          [disableMouseScroll]=\"thumbConfig.disableMouseScroll\"\n                          [position]=\"thumbConfig.position\"\n                          [detach]=\"thumbConfig.detach\">\n            <img *galleryItemDef=\"let item\"\n                 galleryImage\n                 [src]=\"item.thumb\"\n                 fill\n                 [attr.alt]=\"item.alt + '_thumb'\"/>\n          </gallery-thumbs>\n        }\n      </gallery>\n\n      <div class=\"buttons-container\">\n        <button mat-flat-button (click)=\"gallery.play()\">Play</button>\n        <button mat-button color=\"accent\" (click)=\"gallery.stop()\">Stop</button>\n        <button mat-button color=\"primary\" (click)=\"gallery.prev()\">Prev</button>\n        <button mat-button color=\"primary\" (click)=\"gallery.next()\">Next</button>\n        <!--      <button mat-button color=\"primary\" (click)=\"gallery.remove(gallery.galleryRef.currIndex())\">Remove-->\n        <!--      </button>-->\n      </div>\n    </div>\n  }\n\n  <mat-card>\n    <mat-card-content>\n\n      <div class=\"outputs-container\">\n        @if (indexChange(); as indexChange) {\n          <span class=\"chip\" [class.changed]=\"indexChange.active\">\n            <strong>indexChange {{ indexChange.e?.currIndex }}</strong>\n          </span>\n        }\n\n        @if (thumbClick(); as thumbClick) {\n          <span class=\"chip\" [class.changed]=\"thumbClick.active\">\n            <strong>thumbClick {{ thumbClick.e }}</strong>\n          </span>\n        }\n\n        @if (itemClick(); as itemClick) {\n          <span class=\"chip\" [class.changed]=\"itemClick.active\">\n            <strong>itemClick {{ itemClick.e }}</strong>\n          </span>\n        }\n\n        @if (player(); as player) {\n          <span class=\"chip\" [class.changed]=\"player.active\">\n            <strong>playingChange {{ player.e?.isPlaying }}</strong>\n          </span>\n        }\n      </div>\n\n      <div class=\"row\">\n\n        <div class=\"column\">\n\n<!--          <div>-->\n<!--            <mat-form-field>-->\n<!--              <mat-label>Loading strategy</mat-label>-->\n<!--              <mat-select [(ngModel)]=\"config.loadingStrategy\">-->\n<!--                @for (option of loadingStrategies; track option) {-->\n<!--                  <mat-option [value]=\"option\">{{ option }}</mat-option>-->\n<!--                }-->\n<!--              </mat-select>-->\n<!--            </mat-form-field>-->\n<!--          </div>-->\n          <div>\n            <mat-form-field name=\"orientation\">\n              <mat-label>Orientation</mat-label>\n              <mat-select [(ngModel)]=\"config.orientation\">\n                @for (option of orientations; track option) {\n                  <mat-option [value]=\"option\">{{ option }}</mat-option>\n                }\n              </mat-select>\n            </mat-form-field>\n          </div>\n\n          <div>\n            <mat-form-field>\n              <mat-label>Image size</mat-label>\n              <mat-select [(ngModel)]=\"config.imageSize\">\n                @for (option of imageSizes; track option) {\n                  <mat-option [value]=\"option\">{{ option }}</mat-option>\n                }\n              </mat-select>\n            </mat-form-field>\n          </div>\n\n          <div>\n            <mat-form-field>\n              <mat-label>Thumbnails image size</mat-label>\n              <mat-select [(ngModel)]=\"thumbConfig.imageSize\">\n                @for (option of imageSizes; track option) {\n                  <mat-option [value]=\"option\">{{ option }}</mat-option>\n                }\n              </mat-select>\n            </mat-form-field>\n          </div>\n\n          <div>\n            <mat-form-field>\n              <mat-label>Thumbnails Position</mat-label>\n              <mat-select [(ngModel)]=\"thumbConfig.position\" [disabled]=\"!thumbConfig.thumbs\">\n                @for (option of thumbPositions; track option) {\n                  <mat-option [value]=\"option\">{{ option }}</mat-option>\n                }\n              </mat-select>\n            </mat-form-field>\n          </div>\n\n          <div>\n            <mat-form-field>\n              <mat-label>Counter Position</mat-label>\n              <mat-select [(ngModel)]=\"counterConfig.align\" [disabled]=\"!counterConfig.counter\">\n                @for (option of dotsCounterPositions; track option) {\n                  <mat-option [value]=\"option\">{{ option }}</mat-option>\n                }\n              </mat-select>\n            </mat-form-field>\n          </div>\n\n          <div>\n            <mat-form-field>\n              <mat-label>Dots Position</mat-label>\n              <mat-select [(ngModel)]=\"bulletConfig.align\" [disabled]=\"!bulletConfig.bullet\">\n                @for (option of dotsCounterPositions; track option) {\n                  <mat-option [value]=\"option\">{{ option }}</mat-option>\n                }\n              </mat-select>\n            </mat-form-field>\n          </div>\n\n        </div>\n\n        <div class=\"column\">\n          <div>\n            <mat-checkbox [(ngModel)]=\"config.debug\">Debug</mat-checkbox>\n          </div>\n          <!--          <div>-->\n          <!--            <mat-checkbox [(ngModel)]=\"config.autoHeight\" [disabled]=\"config.itemAutosize\">Auto-height</mat-checkbox>-->\n          <!--          </div>-->\n          <div>\n            <mat-checkbox [(ngModel)]=\"config.itemAutosize\">Item autosize</mat-checkbox>\n          </div>\n          <div>\n            <mat-checkbox [(ngModel)]=\"thumbConfig.autosize\" [disabled]=\"!thumbConfig.thumbs\">Thumb autosize\n            </mat-checkbox>\n          </div>\n\n          <div>\n            <mat-form-field>\n              <mat-label>Scroll duration</mat-label>\n              <input matInput [(ngModel)]=\"config.scrollDuration\">\n            </mat-form-field>\n          </div>\n\n          <div>\n            <mat-form-field>\n              <mat-label>Player interval</mat-label>\n              <input matInput [(ngModel)]=\"config.autoplayInterval\">\n            </mat-form-field>\n          </div>\n          <div>\n            <mat-form-field>\n              <mat-label>Resize debounce time</mat-label>\n              <input matInput [(ngModel)]=\"config.resizeDebounceTime\">\n            </mat-form-field>\n          </div>\n\n          <div>\n            <mat-form-field>\n              <mat-label>Scroll Behavior</mat-label>\n              <mat-select [(ngModel)]=\"config.scrollBehavior\">\n                @for (option of scrollBehaviors; track option) {\n                  <mat-option [value]=\"option\">{{ option }}</mat-option>\n                }\n              </mat-select>\n            </mat-form-field>\n          </div>\n\n          <div>\n            <mat-form-field>\n              <mat-label>Direction</mat-label>\n              <mat-select [(ngModel)]=\"dir\">\n                @for (option of directions; track option) {\n                  <mat-option [value]=\"option\">{{ option }}</mat-option>\n                }\n              </mat-select>\n            </mat-form-field>\n          </div>\n        </div>\n\n        <div class=\"column checkboxes\">\n          <div>\n            <mat-checkbox [(ngModel)]=\"config.loop\">Loop navigation</mat-checkbox>\n          </div>\n          <div>\n            <mat-checkbox [(ngModel)]=\"navConfig.nav\">Show navigation</mat-checkbox>\n          </div>\n          <div>\n            <mat-checkbox [(ngModel)]=\"bulletConfig.bullet\">Show bullets</mat-checkbox>\n          </div>\n          <div>\n            <mat-checkbox [(ngModel)]=\"counterConfig.counter\">Show counter</mat-checkbox>\n          </div>\n          <div>\n            <mat-checkbox [(ngModel)]=\"thumbConfig.thumbs\">Show thumbnails</mat-checkbox>\n          </div>\n          <div>\n            <mat-checkbox [(ngModel)]=\"config.centralized\">Centralize Slider</mat-checkbox>\n          </div>\n          <div>\n            <mat-checkbox [(ngModel)]=\"thumbConfig.centralized\" [disabled]=\"!thumbConfig.thumbs\">Centralize Thumbnails\n            </mat-checkbox>\n          </div>\n          <div>\n            <mat-checkbox [(ngModel)]=\"thumbConfig.detach\" [disabled]=\"!thumbConfig.thumbs\">Detach thumbnails\n            </mat-checkbox>\n          </div>\n          <div>\n            <mat-checkbox [(ngModel)]=\"thumbConfig.disabled\" [disabled]=\"!thumbConfig.thumbs\">Disable thumbnails' clicks\n            </mat-checkbox>\n          </div>\n          <div>\n            <mat-checkbox [(ngModel)]=\"config.autoplay\">Auto-play</mat-checkbox>\n          </div>\n\n          <div>\n            <mat-checkbox [(ngModel)]=\"config.disableScroll\">Disable Scroll</mat-checkbox>\n          </div>\n\n          <div>\n            <mat-checkbox [(ngModel)]=\"thumbConfig.disableScroll\" [disabled]=\"!thumbConfig.thumbs\">Disable Thumb\n              Scroll\n            </mat-checkbox>\n          </div>\n\n          <div>\n            <mat-checkbox [(ngModel)]=\"config.disableMouseScroll\">Disable Mouse Scroll</mat-checkbox>\n          </div>\n\n          <div>\n            <mat-checkbox [(ngModel)]=\"thumbConfig.disableMouseScroll\" [disabled]=\"!thumbConfig.thumbs\">Disable Thumb\n              Mouse Scroll\n            </mat-checkbox>\n          </div>\n        </div>\n      </div>\n\n    </mat-card-content>\n  </mat-card>\n</div>\n\n<footer></footer>\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/lab/lab.component.scss",
    "content": ".page-content {\n  padding-bottom: 50px;\n}\n\nbutton {\n  margin: 0.8em 0.5em;\n}\n\ngallery {\n  margin-top: 3em;\n  border-radius: 3px;\n}\n\n.buttons-container {\n  margin: 2em 0;\n  display: flex;\n  justify-content: center;\n}\n\n.row {\n  justify-content: space-around;\n}\n\n.column {\n  align-items: start;\n}\n\n.outputs-container {\n  width: 100%;\n  display: flex;\n  justify-content: center;\n  margin-bottom: 1em;\n}\n\nspan {\n  color: gray;\n  background-color: honeydew;\n  transition: background-color ease-out 1s;\n  box-sizing: border-box;\n  margin: 0.5em;\n  border-radius: 1.5em;\n  padding: 0.6em 1em;\n  font-size: 0.8em;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n}\n\n.changed {\n  background-color: rgb(151, 255, 218) !important;\n  transition: unset !important;\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/lab/lab.component.ts",
    "content": "import { ChangeDetectionStrategy, Component, inject, OnInit, Signal, signal, WritableSignal } from '@angular/core';\nimport { Title } from '@angular/platform-browser';\nimport { FormsModule } from '@angular/forms';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport {\n  GalleryItemData,\n  GalleryConfig,\n  LOADING_STRATEGY,\n  ORIENTATION,\n  THUMB_POSITION,\n  GalleryComponent,\n  GalleryThumbsComponent,\n  GalleryNavComponent,\n  GalleryBulletsComponent,\n  GalleryCounterComponent,\n  GalleryItemDef,\n  ImgRecognizer\n} from 'ng-gallery';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatCheckboxModule } from '@angular/material/checkbox';\nimport { MatOptionModule } from '@angular/material/core';\nimport { MatSelectModule } from '@angular/material/select';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatCardModule } from '@angular/material/card';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\nimport { Pixabay } from '../../service/pixabay.service';\nimport { FooterComponent } from '../../shared/footer/footer.component';\nimport { BasicExampleComponent } from '../examples/basic-example/basic-example';\nimport { Dir, Direction } from '@angular/cdk/bidi';\n\n@Component({\n  host: {\n    'class': 'page'\n  },\n  selector: 'lab',\n  templateUrl: './lab.component.html',\n  styleUrl: './lab.component.scss',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  imports: [\n    // AutoHeight,\n    MatIconModule,\n    GalleryComponent,\n    MatButtonModule,\n    MatCardModule,\n    MatFormFieldModule,\n    MatSelectModule,\n    FormsModule,\n    MatOptionModule,\n    MatCheckboxModule,\n    MatInputModule,\n    FooterComponent,\n    GalleryThumbsComponent,\n    GalleryNavComponent,\n    GalleryBulletsComponent,\n    GalleryCounterComponent,\n    GalleryItemDef,\n    ImgRecognizer,\n    Dir\n    // NgOptimizedImage,\n    // BasicExampleComponent\n  ]\n})\nexport class LabComponent implements OnInit {\n\n  private pixabay: Pixabay = inject(Pixabay);\n\n  private _title: Title = inject(Title);\n\n  show: WritableSignal<boolean> = signal(true);\n\n  photos: Signal<GalleryItemData[]> = toSignal(this.pixabay.getHDImages('tropical'));\n\n  config: GalleryConfig = {\n    loop: true,\n    resizeDebounceTime: 0,\n    scrollDuration: 468,\n    autoplay: false,\n    disableScroll: false,\n    disableMouseScroll: false,\n    centralized: false,\n    imageSize: 'contain',\n    autoplayInterval: 3000,\n    orientation: ORIENTATION.Horizontal,\n    itemAutosize: false,\n    scrollBehavior: 'smooth',\n    debug: true\n  };\n\n  thumbConfig = {\n    thumbs: true,\n    disableScroll: false,\n    disableMouseScroll: false,\n    thumbWidth: 120,\n    thumbHeight: 90,\n    imageSize: 'cover',\n    disabled: false,\n    centralized: false,\n    position: THUMB_POSITION.Bottom,\n    autosize: false,\n    detach: false\n  }\n\n  navConfig = {\n    nav: true\n  };\n\n  bulletConfig = {\n    bullet: true,\n    align: 'bottom',\n    scrollBehavior: 'smooth',\n    disabled: false\n  }\n\n  counterConfig = {\n    counter: true,\n    align: 'top'\n  }\n\n  dir: Direction = 'ltr';\n\n  directions: Direction[] = ['ltr', 'rtl']\n  imageSizes = ['cover', 'contain'];\n  thumbPositions = ['top', 'left', 'right', 'bottom'];\n  loadingStrategies = ['default', 'lazy', 'preload'];\n  orientations = ['vertical', 'horizontal'];\n  dotsCounterPositions = ['top', 'bottom'];\n  scrollBehaviors = ['auto', 'smooth'];\n  loadingAttrs = ['eager', 'lazy'];\n\n  player: WritableSignal<any> = signal<any>({ active: false });\n  itemClick: WritableSignal<any> = signal<any>({ active: false });\n  thumbClick: WritableSignal<any> = signal<any>({ active: false });\n  indexChange: WritableSignal<any> = signal<any>({ active: false });\n\n  ngOnInit(): void {\n    this._title.setTitle('Lab | ng-gallery');\n  }\n\n  restart(): void {\n    this.show.set(false);\n    setTimeout(() => this.show.set(true), 300);\n  }\n\n  onPlayer(e): void {\n    this.updateEvent(this.player, e);\n  }\n\n  onItemClick(e): void {\n    this.updateEvent(this.itemClick, e);\n  }\n\n  onThumbClick(e): void {\n    this.updateEvent(this.thumbClick, e);\n  }\n\n  onIndexChange(e): void {\n    this.updateEvent(this.indexChange, e);\n  }\n\n  private updateEvent(eventState: WritableSignal<any>, e?: any): void {\n    eventState.update(value => ({ ...value, ...{ active: true, e } }));\n    setTimeout(() => {\n      eventState.update(value => ({ ...value, ...{ active: false } }));\n    }, 800);\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/lightbox-example/lightbox-example.component.html",
    "content": "<div class=\"page-title\">\n  <h1>Lightbox Component</h1>\n</div>\n<div class=\"page-content\">\n\n  <!--  <section>-->\n  <!--    <section-title>Overview</section-title>-->\n  <!--    <p>Use the directive <code class=\"hljs\">[lightbox]</code> to open the lightbox on elements clicks</p>-->\n  <!--    <p>Alternatively, you can still use the <code class=\"hljs\">Lightbox</code> service to open the lightbox-->\n  <!--      programmatically.</p>-->\n  <!--  </section>-->\n\n  <section>\n    <!--    <section-title>Usage</section-title>-->\n\n    <!--    <note class=\"info\" style=\"margin-top: -4em\">-->\n    <!--      <p>If you haven't installed the module in step <span class=\"section-number inline-number\">1</span>-->\n    <!--        see the <a mat-button class=\"inline-button\" style=\"padding: 0 0.5em\" color=\"accent\"-->\n    <!--                   [routerLink]=\"['/getting-started/lightbox']\">Getting-->\n    <!--          Started Guide</a></p>-->\n    <!--    </note>-->\n\n    <!--    <div class=\"usage-title\">-->\n    <!--      <span class=\"section-number\">2</span>-->\n    <!--      <p>Create a gallery items array to load it in the gallery</p>-->\n    <!--    </div>-->\n\n    <!--    <hl-code [code]=\"code.loadItems\"/>-->\n\n    <!--    <div class=\"usage-title\">-->\n    <!--      <span class=\"section-number\">3</span>-->\n    <!--      <p>Now that you have gallery items, display their thumbnails in the template and add the directive <code-->\n    <!--        class=\"hljs\">[lightbox]</code>-->\n    <!--        on each item so the clicks open the lightbox.</p>-->\n    <!--    </div>-->\n\n    <!--    <h4>Example</h4>-->\n    <!--    <hl-code [code]=\"code.template\"/>-->\n\n    <!--    <p>If you are using multiple galleries in your app, then each gallery should have an id.</p>-->\n\n    <!--    <h4>Full Example</h4>-->\n\n    <!--    <hl-code [code]=\"code.ex\"/>-->\n\n    <h3>Demo</h3>\n\n    <lightbox #lightbox>\n      <gallery [items]=\"photos()\">\n        <img *galleryItemDef=\"let item\"\n             galleryImage\n             [src]=\"item.src\"\n             fill\n             [attr.alt]=\"item.alt\"/>\n\n        <gallery-nav/>\n\n        <gallery-counter/>\n\n        <gallery-thumbs>\n          <img *galleryItemDef=\"let item\"\n               galleryImage\n               [src]=\"item.thumb\"\n               fill\n               [attr.alt]=\"item.alt + '_thumb'\"/>\n        </gallery-thumbs>\n      </gallery>\n    </lightbox>\n\n    <div class=\"grid\">\n      @for (item of photos(); track i; let i = $index) {\n        <button mat-raised-button\n                class=\"grid-item\"\n                (click)=\"lightbox.showModal(i)\">\n          <img class=\"grid-image\" loading=\"lazy\" [src]=\"item.thumb\"/>\n        </button>\n\n        <!--          <img class=\"grid-image\" loading=\"lazy\" [src]=\"item.data.thumb\"/>-->\n      }\n    </div>\n\n    <p>Lightbox config can be set globally in <code class=\"hljs\">LightboxModule.forRoot(config)</code> or directly\n      with the open function.</p>\n\n    <note>\n      <p>By default, the lightbox uses full screen style on small screens like on mobile phones, but you can force a\n        full screen style on large screens too.</p>\n    </note>\n\n    <!--    <p>For example, open the lightbox in-->\n    <!--      <button mat-button class=\"inline-button\" color=\"accent\"-->\n    <!--              (click)=\"lightbox.open(0, 'lightbox', {panelClass: 'fullscreen'})\">-->\n    <!--        Full Screen-->\n    <!--      </button>-->\n    <!--      mode-->\n    <!--    </p>-->\n\n    <hl-code [code]=\"code.alt\"/>\n\n    <p>List of lightbox options is available in the\n      <a mat-button class=\"inline-button\" color=\"primary\" target=\"_blank\"\n         href=\"https://github.com/MurhafSousli/ngx-gallery/wiki/Lightbox-API\">\n        <fa-icon icon=\"external-link-alt\"/>\n        Lightbox API\n      </a>\n    </p>\n  </section>\n</div>\n<footer></footer>\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/lightbox-example/lightbox-example.component.scss",
    "content": ":host {\n  .mdc-button {\n    ::ng-deep {\n      .mdc-button__label {\n        height: 100%;\n        width: 100%;\n      }\n    }\n  }\n}\n\n.example-card {\n  width: 400px;\n}\n\n.example-header-image {\n  background-image: url('https://material.angular.io/shiba1.d84e1a8ed94377452dbf.jpg');\n  background-size: cover;\n}\n\n.grid-item {\n  margin: 1em;\n  border-radius: 0.2em;\n  overflow: hidden;\n  padding: 0;\n  width: 150px;\n  height: 90px;\n\n  @media only screen and (max-width: 480px) {\n    margin: 0.4em;\n    width: 100px;\n    height: 80px;\n  }\n}\n\n.grid {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  flex-wrap: wrap;\n  margin: 3em 0;\n}\n\n.grid-image {\n  display: block;\n  width: 100%;\n  height: 100%;\n  background-size: cover;\n  background-position: center center;\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/lightbox-example/lightbox-example.component.ts",
    "content": "import { ChangeDetectionStrategy, Component, inject, OnDestroy, OnInit, Signal } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport { CommonModule } from '@angular/common';\nimport { Title } from '@angular/platform-browser';\nimport { MatButtonModule } from '@angular/material/button';\nimport { FontAwesomeModule } from '@fortawesome/angular-fontawesome';\nimport {\n  GalleryBulletsComponent,\n  GalleryCounterComponent,\n  GalleryItemData,\n  GalleryNavComponent,\n  GalleryThumbsComponent, ImgRecognizer\n} from 'ng-gallery';\nimport { LightboxComponent, LightboxModule, provideLightboxOptions } from 'ng-gallery/lightbox';\nimport { Pixabay } from '../../service/pixabay.service';\nimport { FooterComponent } from '../../shared/footer/footer.component';\nimport { HlCodeComponent } from '../../shared/hl-code/hl-code.component';\nimport { NoteComponent } from '../../shared/note/note.component';\nimport { SectionTitleComponent } from '../../shared/section-title/section-title.component';\nimport { toSignal } from '@angular/core/rxjs-interop';\n\n@Component({\n  host: {\n    'class': 'page'\n  },\n  selector: 'lightbox-example',\n  templateUrl: './lightbox-example.component.html',\n  styleUrl: './lightbox-example.component.scss',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  providers: [\n    provideLightboxOptions({\n      keyboardShortcuts: false\n    })\n  ],\n  imports: [\n    CommonModule,\n    LightboxModule,\n    SectionTitleComponent,\n    NoteComponent,\n    MatButtonModule,\n    RouterLink,\n    HlCodeComponent,\n    FontAwesomeModule,\n    FooterComponent,\n    GalleryBulletsComponent,\n    GalleryCounterComponent,\n    GalleryNavComponent,\n    GalleryThumbsComponent,\n    ImgRecognizer,\n    LightboxComponent,\n  ]\n})\nexport class LightboxExampleComponent implements OnInit {\n\n  private pixabay: Pixabay = inject(Pixabay);\n\n  private _title: Title = inject(Title);\n\n  code: any = code;\n  photos: Signal<GalleryItemData[]> = toSignal(this.pixabay.getHDImages('sea'));\n\n  ngOnInit(): void {\n    this._title.setTitle('Lightbox | ng-gallery');\n  }\n}\n\nconst code = {\n  loadItems: `items: GalleryItemData[] = [...];\nconst galleryRef = this.gallery.ref();\ngalleryRef.load(items)`,\n  template: `<div class=\"grid-item\"\n     *ngFor=\"let item of items; let i = index\"\n     [lightbox]=\"i\">\n  <img [src]=\"item.data.thumbnail\"/>\n</div>`,\n  ex: `import { Component, OnInit } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { GalleryModule, Gallery, GalleryItemData } from 'ng-gallery';\nimport { LightboxModule } from 'ng-gallery/lightbox';\n\n@Component({\n  template: \\`\n    <div class=\"grid\">\n      <div class=\"grid-item\"\n           *ngFor=\"let item of space$ | async; let i = index\"\n           [lightbox]=\"i\"\n           [gallery]=\"galleryId\">\n        <img [src]=\"item.data.thumbnail\"/>\n      </div>\n    </div>\n  \\`,\n  imports: [CommonModule, LightboxModule]\n})\nexport class AppComponent implements OnInit {\n\n  galleryId = 'myLightbox';\n  items: GalleryItemData[];\n\n  constructor(public gallery: Gallery) { }\n\n  ngOnInit() {\n    // Load items into gallery\n    const galleryRef = this.gallery.ref(this.galleryId);\n    galleryRef.load(this.items);\n  }\n}`,\n  alt: `import { Component, OnInit } from '@angular/core';\nimport { Gallery, GalleryItemData } from 'ng-gallery';\nimport { Lightbox } from 'ng-gallery/lightbox';\n\n@Component({\n  template: \\`\n    <button (click)=\"openInFullScreen(4)\">Open image 5</button>\n  \\`,\n  standalone: true\n})\nexport class AppComponent implements OnInit {\n\n  galleryId = 'myLightbox';\n  items: GalleryItemData[];\n\n  constructor(public gallery: Gallery, private lightbox: Lightbox) { }\n\n  ngOnInit() {\n    // Load items into gallery\n    const galleryRef = this.gallery.ref(this.galleryId);\n    galleryRef.load(this.items);\n  }\n\n  openInFullScreen(index: number) {\n    this.lightbox.open(index, this.galleryId, {\n      panelClass: 'fullscreen'\n    });\n  }\n}`\n};\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/not-found/not-found.component.html",
    "content": "<div class=\"not-found\" fxLayout=\"column\" fxLayoutAlign=\"center center\">\n  <div>\n    <i class=\"fa fa-exclamation-triangle\" aria-hidden=\"true\"></i>\n  </div>\n  Page was not found!\n</div>\n\n<footer></footer>\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/not-found/not-found.component.scss",
    "content": ":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;\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/not-found/not-found.component.ts",
    "content": "import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';\nimport { Title } from '@angular/platform-browser';\nimport { FooterComponent } from '../../shared/footer/footer.component';\n\n@Component({\n  host: {\n    'class': 'page'\n  },\n  selector: 'not-found',\n  templateUrl: './not-found.component.html',\n  styleUrl: './not-found.component.scss',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  imports: [FooterComponent]\n})\nexport class NotFoundComponent implements OnInit {\n\n  constructor(private _title: Title) {\n  }\n\n  ngOnInit() {\n    this._title.setTitle('404 | ng-gallery');\n  }\n\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/templates-example/slide-text.animation.ts",
    "content": "import { trigger, style, transition, animate, state } from '@angular/animations';\n\nexport const slideInAnimation = trigger('slideAnimation', [\n  state('in', style({ transform: 'translateY(0)', opacity: 1 })),\n  transition(':enter', [\n    style({ transform: 'translateY(-70%)', opacity: 0 }),\n    animate('400ms ease-in')\n  ]),\n  transition(':leave', [\n    animate('400ms ease-in', style({ transform: 'translateY(-70%)', opacity: 0 }))\n  ])\n]);\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/templates-example/templates-example.component.html",
    "content": "<div class=\"page-title\">\n  <h1>Custom Templates Example</h1>\n</div>\n<div class=\"page-content\">\n  <section>\n    <section-title>Overview</section-title>\n\n    <p>This package provides a custom set of directives to make it easy to customize different gallery templates.</p>\n\n    <p>These directives are: <code>*galleryItemDef</code>, <code>*galleryImageDef</code>, <code>*galleryThumbDef</code>\n      and <code>*galleryBoxDef</code></p>\n  </section>\n\n  @if (media$ | async; as config) {\n    <gallery id=\"mixed\">\n      <gallery-thumbs [thumbWidth]=\"config.thumbWidth\" [thumbHeight]=\"config.thumbHeight\"/>\n\n      <ng-container *galleryImageDef=\"let item; active as active\">\n        @if (active) {\n          <span @slideAnimation class=\"item-text\">\n            {{ item?.alt }}\n          </span>\n        }\n      </ng-container>\n\n      <ng-container *galleryThumbDef=\"let item; type as type\">\n        @if (type === 'youtube') {\n          <span class=\"item-type\">\n            <fa-icon [icon]=\"youtubeIcon\" size=\"lg\"/>\n          </span>\n        } @else if(type === 'vimeo'){\n          <span class=\"item-type\">\n            <fa-icon [icon]=\"vimeoIcon\" size=\"lg\" />\n          </span>\n        } @else if (type === 'video') {\n          <span class=\"item-type\">\n            <fa-icon [icon]=\"videoIcon\" size=\"lg\"/>\n          </span>\n        }\n      </ng-container>\n    </gallery>\n  }\n\n  <note>Please consider having a look at wiki docs as well\n    <a mat-button class=\"inline-button\" color=\"primary\" target=\"_blank\"\n       href=\"https://github.com/MurhafSousli/ngx-gallery/wiki/Custom-Templates\">\n      <fa-icon icon=\"external-link-alt\"></fa-icon>\n      Gallery Templates\n    </a>\n    and\n    <a mat-button class=\"inline-button\" color=\"primary\" target=\"_blank\"\n       href=\"https://github.com/MurhafSousli/ngx-gallery/wiki/Custom-templates-for-lightbox\">\n      <fa-icon icon=\"external-link-alt\"></fa-icon>\n      Lightbox Templates\n    </a>\n  </note>\n\n  <section>\n    <section-title>GalleryItemDef</section-title>\n\n    <p><code>*galleryItemDef</code> overrides the gallery item template, useful if you want create your own gallery\n      template for any type from scratch.</p>\n\n    <h3>Example:</h3>\n    <hl-code [code]=\"code.customItemTemplate\"/>\n  </section>\n\n  <section>\n    <section-title>GalleryImageDef</section-title>\n\n    <p><code>*galleryImageDef</code> applies only on image items - the image will still be displayed, but this adds an\n      custom inner template, useful to add text overlay on top of the image.</p>\n\n    <h3>Example:</h3>\n    <hl-code [code]=\"code.customImageTemplate\"/>\n  </section>\n\n  <section>\n    <section-title>GalleryThumbDef</section-title>\n\n    <p><code>*galleryThumbDef</code> applies on all thumbnails types, image will still displayed, but adds an custom\n      inner template.</p>\n\n    <h3>Example:</h3>\n    <p>The following adds a Youtube icon on Youtube thumbnails, a Vimeo icon on Vimeo thumbnails, and video icon on video thumbnails</p>\n\n    <hl-code [code]=\"code.customThumbTemplate\"/>\n  </section>\n\n  <section>\n    <h3>Passing in data to a custom template</h3>\n\n    <p>When using one of the previous templates above, additional context data can be used to render the data as\n      needed.</p>\n\n    <h3>Available data:</h3>\n\n    <ul>\n      <li><code>let item</code> Object of the item, including properties like <code>src</code>, <code>thumb</code>and\n        <code>alt</code> ... etc.\n      </li>\n      <li><code>let index = index</code> Index of the item.</li>\n      <li><code>let type = type</code> Type of the item, default ones are <code>image</code>, <code>video</code>,\n        <code>youtube</code>, <code>vimeo</code> and <code>iframe</code>.\n      </li>\n      <li><code>let active = active</code> True if this item is the active one.</li>\n      <li><code>let count = count</code> The number of total items.</li>\n      <li><code>let first = first</code> True if this item is first.</li>\n      <li><code>let last = last</code> True if this item is last.</li>\n    </ul>\n  </section>\n\n  <section>\n    <section-title>GalleryBoxDef</section-title>\n\n    <p><code>*galleryBoxDef</code> Adds a single static template on top of the gallery items.</p>\n\n    <h3>Example:</h3>\n    <hl-code [code]=\"code.customBoxTemplate\"/>\n\n    <h3>Available data:</h3>\n\n    <ul>\n      <li><code>let state = state</code> Gallery state object.</li>\n      <li><code>let config = config</code> Gallery config object.</li>\n    </ul>\n  </section>\n</div>\n\n<footer></footer>\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/templates-example/templates-example.component.scss",
    "content": "::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    .item-type {\n      padding: 6px;\n      width: 36px;\n      height: 36px;\n      border-radius: 50%;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      background-color: rgba(black, 0.5);\n    }\n\n    .item-text {\n      position: absolute;\n      top: 0;\n      left: 0;\n      right: 0;\n      margin: 3em auto 0;\n      width: 100%;\n      max-width: 500px;\n      padding: 20px 25px;\n      text-align: justify;\n\n      letter-spacing: 1px;\n      filter: drop-shadow(0px 0px 2px rgba(0, 0, 0, 0.3));\n      background: #ffffffd9;\n      color: black;\n      border-radius: 8px;\n    }\n  }\n}\n\nul {\n  list-style: none\n}\n\nli {\n  display: flex;\n  align-items: center;\n\n  &:before {\n    margin-right: 10px;\n    content: \"•\";\n    color: var(--accent-color);\n    font-size: 2em;\n  }\n}\n\ncode {\n  flex: 0 0 auto;\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/pages/templates-example/templates-example.component.ts",
    "content": "import { Component, effect, OnInit, Signal, viewChild } from '@angular/core';\nimport { Title } from '@angular/platform-browser';\nimport { CommonModule } from '@angular/common';\nimport {\n  GalleryConfig,\n  GalleryItemData,\n  ITEM_TYPE,\n  IframeItemData,\n  ImageItemData,\n  VideoItemData,\n  YoutubeItemData,\n  GalleryModule,\n  GalleryComponent,\n  VimeoItemData,\n  GalleryThumbsComponent\n} from 'ng-gallery';\nimport { Observable, map } from 'rxjs';\nimport { FontAwesomeModule } from '@fortawesome/angular-fontawesome';\nimport { faYoutube } from '@fortawesome/free-brands-svg-icons/faYoutube';\nimport { faVimeo } from '@fortawesome/free-brands-svg-icons/faVimeo';\nimport { faVideo } from '@fortawesome/free-solid-svg-icons/faVideo';\n\nimport { slideInAnimation } from './slide-text.animation';\nimport { FooterComponent } from '../../shared/footer/footer.component';\nimport { HlCodeComponent } from '../../shared/hl-code/hl-code.component';\nimport { SectionTitleComponent } from '../../shared/section-title/section-title.component';\nimport { MatButtonModule } from '@angular/material/button';\nimport { NoteComponent } from '../../shared/note/note.component';\n\n@Component({\n  host: {\n    'class': 'page'\n  },\n  selector: 'templates-example',\n  templateUrl: './templates-example.component.html',\n  styleUrl: './templates-example.component.scss',\n  animations: [slideInAnimation],\n  imports: [\n    CommonModule,\n    SectionTitleComponent,\n    GalleryModule,\n    HlCodeComponent,\n    FooterComponent,\n    FontAwesomeModule,\n    MatButtonModule,\n    NoteComponent,\n    GalleryThumbsComponent\n  ]\n})\nexport class TemplatesExampleComponent {\n\n  readonly arr = data;\n  readonly code = code;\n  readonly media$: Observable<any>;\n  readonly youtubeIcon = faYoutube;\n  readonly vimeoIcon = faVimeo;\n  readonly videoIcon = faVideo;\n\n  gallery: Signal<GalleryComponent> = viewChild(GalleryComponent);\n\n  constructor(private _title: Title) {\n    this._title.setTitle('Custom Templates | ng-gallery');\n\n    // this.media$ = mediaObserver.asObservable().pipe(\n    //   map((res: MediaChange[]) => {\n    //     if (res.some((x => x.mqAlias === 'sm' || x.mqAlias === 'xs'))) {\n    //       return {\n    //         thumbWidth: 80,\n    //         thumbHeight: 80\n    //       };\n    //     }\n    //     return {\n    //       thumbWidth: 120,\n    //       thumbHeight: 90\n    //     };\n    //   })\n    // );\n\n    // effect(() => {\n    //   const gallery: GalleryComponent = this.gallery();\n    //   if (gallery) {\n    //   this.arr.map((item: GalleryItemData) => {\n    //     switch (item.type) {\n    //       case GalleryItemTypes.Image:\n    //         gallery.addImage(item);\n    //         break;\n    //       case GalleryItemTypes.Video:\n    //         gallery.addVideo(item);\n    //         break;\n    //       case GalleryItemTypes.Youtube:\n    //         gallery.addYoutube(item);\n    //         break;\n    //       case GalleryItemTypes.Vimeo:\n    //         gallery.addVimeo(item);\n    //         break;\n    //       default:\n    //         gallery.addIframe(item);\n    //     }\n    //   });\n    // }\n    //   }, { allowSignalWrites: true });\n  }\n}\n\nconst data: GalleryItemData[] = [\n  {\n    type: 'image',\n    src: 'img/img13.jpg',\n    thumb: 'img/thumb/img13.jpg',\n    alt: '🐓Scelerisque dapibus fringilla consequat scelerisque torquent senectus porttitor, placerat fames convallis molestie lobortis diam aliquam'\n  } as ImageItemData,\n  {\n    type: 'image',\n    src: 'img/img11.jpg',\n    thumb: 'img/thumb/img11.jpg',\n    alt: '🐦Lorem ipsum curabitur auctor netus facilisis inceptos vivamus fusce inceptos, ullamcorper ipsum id pharetra curabitur leo curabitur.'\n  } as ImageItemData,\n  {\n    type: 'image',\n    src: 'img/img3.jpg',\n    thumb: 'img/thumb/img3.jpg',\n    alt: '🐯Iaculis eros leo interdum erat tellus primis pharetra pulvinar, elit risus blandit tempus praesent himenaeos porta, neque elit neque ullamcorper ipsum curabitur at tempus aliquet quam fringilla.'\n  } as ImageItemData,\n  {\n    type: 'image',\n    src: 'img/img4.jpg',\n    thumb: 'img/thumb/img4.jpg',\n    alt: '🐅Morbi etiam interdum velit lacinia platea magna libero curae auctor'\n  } as ImageItemData,\n  {\n    type: 'video',\n    autoplay: false,\n    controls: true,\n    mute: null,\n    loop: true,\n    thumb: 'https://images.pond5.com/orangutan-sitting-tree-and-attentively-footage-074672817_iconl.jpeg',\n    poster: 'https://images.pond5.com/orangutan-sitting-tree-and-attentively-footage-074672817_iconl.jpeg',\n    src: [\n      {\n        url: 'https://videos.pond5.com/orangutan-sitting-tree-and-attentively-footage-074672817_main_xxl.mp4',\n        type: 'video/mp4'\n      }\n    ]\n  } as VideoItemData,\n  {\n    type: 'youtube',\n    autoplay: false,\n    src: 'b7Cl7S0pLRw'\n  } as YoutubeItemData,\n  {\n    type: 'vimeo',\n    autoplay: false,\n    src: '124139626'\n  } as VimeoItemData,\n  {\n    type: 'iframe',\n    src: 'https://ngx-scrollbar.netlify.app/',\n    thumb: 'https://user-images.githubusercontent.com/8130692/64606830-d4006f00-d3cf-11e9-9874-c75269fa3a9c.png'\n  } as IframeItemData\n];\n\nconst code = {\n  customItemTemplate: `<gallery>\n  <div *galleryItemDef=\"let item; let type = type\">\n    <div *ngIf=\"type === 'image'\">\n      <img [src]=\"item.src\">\n    </div>\n    <div *ngIf=\"type === 'video'\">\n      <video>\n        <source src=\"item.src\">\n      </video>\n    </div>\n  </div>\n</gallery>`,\n  customImageTemplate: `<gallery>\n  <ng-container *galleryImageDef=\"let item; let active = active\">\n    <div *ngIf=\"active\" class=\"item-text\">\n      {{ item?.alt }}\n    </div>\n  </ng-container>\n</gallery>`,\n  customThumbTemplate: `<gallery>\n  <ng-container *galleryThumbDef=\"let item; let type = type\">\n    <span *ngIf=\"type === 'youtube'\" class=\"item-type\">\n      <fa-icon [icon]=\"youtubeIcon\" size=\"lg\"></fa-icon>\n    </span>\n    <span *ngIf=\"type === 'vimeo'\" class=\"item-type\">\n      <fa-icon [icon]=\"vimeoIcon\" size=\"lg\"></fa-icon>\n    </span>\n    <span *ngIf=\"type === 'video'\" class=\"item-type\">\n      <fa-icon [icon]=\"videoIcon\" size=\"lg\"></fa-icon>\n    </span>\n  </ng-container>\n</gallery>`,\n  customBoxTemplate: `<gallery>\n  <div *galleryBoxDef=\"let state = state; let config = config\">\n    {{ state.currIndex + 1 }} / {{ state.items.length }}\n  </div>\n</gallery>`,\n  component: `import { Component, OnInit } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { GalleryModule, Gallery, GalleryRef } from 'ng-gallery';\n\n@Component({\n  selector: 'example-component',\n  templateUrl: './example-templates.html',\n  imports: [CommonModule, GalleryModule]\n})\nexport class ExampleComponent implements OnInit {\n\n  galleryId = 'mixedExample';\n\n  constructor(private gallery: Gallery) { }\n\n  ngOnInit() {\n    const galleryRef: GalleryRef = this.gallery.ref(this.galleryId);\n\n    galleryRef.addImage({\n      src: 'IMAGE_URL',\n      thumb: '(OPTIONAL)IMAGE_THUMBNAIL_URL',\n      alt: 'Some title'\n    });\n\n    galleryRef.addVideo({\n      src: 'VIDEO_URL',\n      thumb: '(OPTIONAL)VIDEO_THUMBNAIL_URL',\n      poster: '(OPTIONAL)VIDEO_POSTER_URL'\n    });\n\n    // Add a video item with multiple url sources\n    galleryRef.addVideo({\n      src: [\n        { url: 'MP4_URL', type: 'video/mp4' },\n        { url: 'OGG_URL', type: 'video/ogg' }\n      ],\n      thumb: '(OPTIONAL)VIDEO_THUMBNAIL_URL',\n      poster: '(OPTIONAL)VIDEO_POSTER_URL'\n    });\n\n    galleryRef.addYoutube({\n      src: 'VIDEO_ID'\n    });\n\n    galleryRef.addVimeo({\n      src: 'VIDEO_ID'\n    });\n\n    galleryRef.addIframe({\n      src: 'IFRAME_URL',\n      thumb: '(OPTIONAL)IMAGE_THUMBNAIL_URL'\n    });\n  }\n}`\n};\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/service/pixabay.model.ts",
    "content": "export interface PixabayModel {\n  total: number;\n  totalHits: number;\n  hits: Hit[];\n}\n\nexport interface Hit {\n  id: number;\n  pageURL: string;\n  type: string;\n  tags: string[];\n  previewURL: string;\n  previewWidth: number;\n  previewHeight: number;\n  webformatURL: string;\n  webformatWidth: number;\n  webformatHeight: number;\n  imageWidth: number;\n  imageHeight: number;\n  imageSize: number;\n  views: number;\n  downloads: number;\n  favorites: number;\n  likes: number;\n  comments: number;\n  user_id: number;\n  user: string;\n  userImageURL: string;\n}\nexport interface PixabayHDModel {\n  total: number;\n  totalHits: number;\n  hits: Hit2[];\n}\n\nexport interface Hit2 {\n  id_hash: string;\n  type: string;\n  previewURL: string;\n  previewWidth: number;\n  previewHeight: number;\n  webformatURL: string;\n  webformatWidth: number;\n  webformatHeight: number;\n  largeImageURL: string;\n  fullHDURL: string;\n  imageWidth: number;\n  imageHeight: number;\n  imageSize: number;\n  imageURL: string;\n  user_id: number;\n  user: string;\n  userImageURL: string;\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/service/pixabay.service.ts",
    "content": "import { inject, Injectable } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { Observable, shareReplay, map } from 'rxjs';\n\nimport { ImageItem, GalleryItemData } from 'ng-gallery';\n\nimport { Hit2, PixabayHDModel } from './pixabay.model';\n\n@Injectable({\n  providedIn: 'root'\n})\nexport class Pixabay {\n\n  private readonly API_KEY: string = '560162-704dd2880c027f22c62ab7941';\n\n  private _http: HttpClient = inject(HttpClient);\n\n  getHDImages(key: string): Observable<GalleryItemData[]> {\n    const URL = `https://pixabay.com/api/?key=${ this.API_KEY }&q=${ encodeURIComponent(key) }&response_group=high_resolution&editors_choice=true&per_page=18&image_type=photo`;\n    return this._http.get(URL).pipe(\n      map((res: PixabayHDModel) => {\n        return res.hits.map((item: Hit2, i: number) => {\n          return {\n            src: item.largeImageURL,\n            thumb: item.previewURL,\n            alt: `photo-${ i }`\n          };\n        });\n      }),\n      shareReplay(1)\n    );\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/badges/badges.component.html",
    "content": "<!--GITHUB STAR-->\n<iframe src=\"https://ghbtns.com/github-btn.html?user=murhafsousli&repo=ngx-gallery&type=star&count=true\" frameborder=\"0\"\n        scrolling=\"0\" width=\"92px\" height=\"20px\"></iframe>\n\n<a href=\"https://www.npmjs.com/package/ng-gallery\">\n  <img data-canonical-src=\"https://www.npmjs.com/package/ng-gallery\"\n       src=\"https://img.shields.io/npm/v/ng-gallery.svg?maxAge=2592000?style=plastic\">\n</a>\n<a href=\"https://stackblitz.com/edit/ngx-gallery\">\n  <img data-canonical-src=\"https://stackblitz.com/edit/ngx-gallery\"\n       src=\"https://img.shields.io/badge/stackblitz-online-orange.svg\">\n</a>\n\n<a href=\"https://github.com/MurhafSousli/ngx-gallery/actions?query=workflow%3Atests\">\n  <img src=\"https://github.com/MurhafSousli/ngx-gallery/workflows/tests/badge.svg\">\n</a>\n\n<a href=\"https://www.npmjs.com/package/ng-gallery\">\n  <img data-canonical-src=\"https://www.npmjs.com/package/ng-gallery\"\n       src=\"https://img.shields.io/npm/dt/ng-gallery.svg?maxAge=2592000?style=plastic\">\n</a>\n<a href=\"https://www.npmjs.com/package/ng-gallery\">\n  <img src=\"https://img.shields.io/npm/dm/ng-gallery.svg\"\n       data-canonical-src=\"https://www.npmjs.com/package/ng-gallery\">\n</a>\n<a href=\"https://github.com/MurhafSousli/ngx-gallery/blob/master/LICENSE\">\n  <img\n    src=\"https://camo.githubusercontent.com/25d313ccfafb08618b31899a22af11a5b8a79aad/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f6c2f657870726573732e7376673f6d61784167653d32353932303030\"\n    alt=\"npm\" data-canonical-src=\"https://img.shields.io/npm/l/express.svg?maxAge=2592000\" style=\"max-width:100%;\">\n</a>\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/badges/badges.component.ts",
    "content": "import { Component } from '@angular/core';\n\n@Component({\n  selector: 'badges',\n  templateUrl: './badges.component.html',\n  styles: [`\n    :host {\n      margin-bottom: 2em;\n      display: flex;\n      flex-wrap: wrap;\n      padding: 1em;\n      justify-content: center;\n    }\n\n    a {\n      text-decoration: none;\n      margin-left: 0.3em;\n    }\n  `]\n})\nexport class BadgesComponent {\n\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/footer/footer.component.html",
    "content": "<div fxLayout=\"column\" fxLayoutAlign=\"center center\">\n\n  <h4><p>Images from <a href=\"https://pixabay.com/\">Pixabay</a></p></h4>\n\n  <h4 fxLayout fxLayoutAlign=\"center center\">\n    <p>© {{ todayDate | date: 'YYYY' }} Angular Gallery</p>\n    <iframe src=\"https://ghbtns.com/github-btn.html?user=murhafsousli&repo=ngx-gallery&type=star&count=true\"\n            frameborder=\"0\" scrolling=\"0\" width=\"85px\" height=\"20px\"></iframe>\n  </h4>\n\n  <div fxLayout fxLayoutAlign=\"center center\">\n    <h4 style=\"margin-right: 15px\">Created by Murhaf Sousli</h4>\n\n    <a mat-icon-button class=\"icon twitter\" href=\"https://twitter.com/MurhafSousli\">\n      <fa-icon [icon]=\"['fab', 'twitter']\" size=\"lg\"></fa-icon>\n    </a>\n    <a mat-icon-button class=\"icon github\" href=\"https://github.com/MurhafSousli\">\n      <fa-icon [icon]=\"['fab', 'github']\" size=\"lg\"></fa-icon>\n    </a>\n  </div>\n</div>\n\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/footer/footer.component.scss",
    "content": ":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  flex-direction: column;\n  color: #fff;\n  padding: 3em 2em;\n  font-size: .9em;\n  font-family: \"Roboto\", \"Helvetica Neue\", sans-serif;\n\n  p {\n    margin-right: 0.5em;\n  }\n\n  h3 {\n    font-weight: normal;\n    color: white;\n    text-align: center;\n    margin-top: 0;\n    margin-bottom: 1.5em;\n\n    &:last-child {\n      margin-bottom: 0;\n    }\n  }\n\n  a {\n    color:  var(--danger-color);\n    text-decoration: underline;\n  }\n}\n\n.icon {\n  margin: 5px;\n}\n\nfa-icon {\n  margin: 0 0.3em;\n}\n\nfa-icon {\n  color: white;\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/footer/footer.component.ts",
    "content": "import { ChangeDetectionStrategy, Component } from '@angular/core';\nimport { DatePipe } from '@angular/common';\nimport { FontAwesomeModule } from '@fortawesome/angular-fontawesome';\nimport { MatButtonModule } from '@angular/material/button';\n\n@Component({\n  selector: 'footer',\n  templateUrl: './footer.component.html',\n  styleUrl: './footer.component.scss',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  imports: [MatButtonModule, FontAwesomeModule, DatePipe]\n})\nexport class FooterComponent {\n  todayDate: number = Date.now();\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/gallery-mock-dialog.ts",
    "content": "import { Component, Inject } from '@angular/core';\nimport { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from '@angular/material/dialog';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatIconModule } from '@angular/material/icon';\n\n@Component({\n  selector: 'gallery-mock-dialog',\n  template: `\n    <mat-dialog-content>\n      <div fxLayout fxLayoutAlign=\"center center\" class=\"{{data.className}}\">\n        <mat-icon>{{data.icon}}</mat-icon>\n        <span>{{data.text}}</span>\n      </div>\n    </mat-dialog-content>\n    <mat-dialog-actions>\n      <button mat-button (click)=\"dialogRef.close()\"><b>OK</b></button>\n    </mat-dialog-actions>\n  `,\n  styles: [`\n    div {\n      font-size: 20px;\n    }\n    mat-icon {\n      width: 60px;\n      height: 60px;\n      font-size: 60px;\n      margin: 10px;\n    }\n  `],\n  imports: [MatDialogModule, MatIconModule, MatButtonModule]\n})\nexport class GalleryMockDialog {\n  constructor(public dialogRef: MatDialogRef<GalleryMockDialog>, @Inject(MAT_DIALOG_DATA) public data: any) {\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/hl-code/hl-code.component.html",
    "content": "@if (disabled) {\n  <div class=\"code-wrapper\">\n    <pre>\n      <code class=\"hljs\" [innerHTML]=\"code\"></code>\n    </pre>\n  </div>\n} @else {\n  <ng-scrollbar>\n    <div class=\"code-wrapper\">\n      <pre>\n        <code [highlightAuto]=\"code\"></code>\n      </pre>\n    </div>\n  </ng-scrollbar>\n}\n\n<div class=\"copy-button-wrapper\">\n  <button mat-icon-button\n          color=\"primary\"\n          matTooltipClass=\"copy-tooltip\"\n          matTooltip=\"Copy code\"\n          matTooltipPosition=\"before\"\n          [cdkCopyToClipboard]=\"code\"\n          (cdkCopyToClipboardCopied)=\"snackBar.open(' Code copied!', null, { duration: 3000 })\">\n    <mat-icon class=\"md-24\" aria-label=\"Copy code\">content_paste</mat-icon>\n  </button>\n</div>\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/hl-code/hl-code.component.scss",
    "content": "@use '../../../mixin';\n\n:host {\n  display: flex;\n  flex-direction: column;\n  position: relative;\n  min-width: 100%;\n  margin-top: 2em;\n  margin-bottom: 3em;\n  background: rgba(0, 0, 0, .01);\n  overflow: hidden;\n  border-radius: 0.5em;\n  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);\n\n  --scrollbar-border-radius: 4px;\n  --scrollbar-hover-thickness: 10;\n  --scrollbar-thickness: 5;\n  --scrollbar-track-wrapper-transition: all 100ms ease;\n}\n\npre {\n  min-height: 100%;\n  min-width: 100%;\n  margin: 0;\n  display: inline-flex;\n  word-wrap: normal;\n\n  code {\n    display: inline-block;\n    height: 100%;\n    padding: 1.7em 2em !important;\n    border-radius: 0;\n    flex: 1;\n    margin: 0;\n    box-shadow: none;\n    overflow: hidden;\n    border: none;\n    min-height: 100%;\n  }\n}\n\n.copy-button-wrapper {\n  position: absolute;\n  right: 10px;\n  top: 10px;\n  display: flex;\n  align-items: center;\n}\n\nspan {\n  margin-left: 10px;\n}\n\npre {\n  min-height: 100%;\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/hl-code/hl-code.component.ts",
    "content": "import { Component, Input, inject, ChangeDetectionStrategy, booleanAttribute } from '@angular/core';\nimport { ClipboardModule } from '@angular/cdk/clipboard';\nimport { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatTooltip } from '@angular/material/tooltip';\nimport { NgScrollbar } from 'ngx-scrollbar';\nimport { HighlightAuto } from 'ngx-highlightjs';\n\n@Component({\n  selector: 'hl-code',\n  templateUrl: './hl-code.component.html',\n  styleUrl: './hl-code.component.scss',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  imports: [MatSnackBarModule, NgScrollbar, MatButtonModule, ClipboardModule, MatIconModule, HighlightAuto, MatTooltip]\n})\nexport class HlCodeComponent {\n\n  readonly snackBar: MatSnackBar = inject(MatSnackBar);\n\n  @Input() code: string;\n  @Input() track: string = 'vertical';\n  @Input({ transform: booleanAttribute }) disabled: boolean;\n\n}\n\n\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/menu/menu.component.html",
    "content": "<div class=\"menu\" [class.menu-toolbar]=\"toolbar\">\n  <a mat-button routerLink=\".\" routerLinkActive=\"active\" [routerLinkActiveOptions]=\"{ exact: true }\">Home</a>\n  <a mat-button routerLink=\"getting-started\" routerLinkActive=\"active\">Getting Started</a>\n  <a mat-button routerLink=\"gallery\" routerLinkActive=\"active\">Gallery</a>\n  <a mat-button routerLink=\"lightbox\" routerLinkActive=\"active\">Lightbox</a>\n  <a mat-button routerLink=\"gallerize\" routerLinkActive=\"active\">Gallerize</a>\n  <a mat-button routerLink=\"custom-templates\" routerLinkActive=\"active\">Custom Templates</a>\n  <a mat-button routerLink=\"lab\" routerLinkActive=\"active\">Lab</a>\n  <a mat-button href=\"https://github.com/MurhafSousli/ng-gallery\">\n    <fa-icon [icon]=\"['fab', 'github']\" size=\"lg\"></fa-icon>\n  </a>\n</div>\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/menu/menu.component.scss",
    "content": "@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: row;\n\n  a.mdc-button {\n    width: unset;\n    height: 36px;\n    line-height: 36px;\n  }\n}\n\na.mdc-button {\n  height: var(--header-height);\n  line-height: var(--header-height);\n  width: 220px;\n  color: white;\n\n  &.active {\n    background: rgba(0, 0, 0, .2);\n  }\n\n  &:hover {\n    background: rgba(0, 0, 0, .4);\n  }\n}\n\n.menu {\n  display: flex;\n  flex-direction: column;\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/menu/menu.component.ts",
    "content": "import { Component, ChangeDetectionStrategy, Input } from '@angular/core';\nimport { RouterLink, RouterLinkActive } from '@angular/router';\nimport { MatButtonModule } from '@angular/material/button';\nimport { FontAwesomeModule } from '@fortawesome/angular-fontawesome';\n\n@Component({\n  selector: 'app-menu',\n  templateUrl: './menu.component.html',\n  styleUrl: './menu.component.scss',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  imports: [MatButtonModule, RouterLink, RouterLinkActive, FontAwesomeModule]\n})\nexport class MenuComponent {\n  @Input() toolbar = true;\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/note/note.component.scss",
    "content": ":host {\n  display: block;\n\n  &.info {\n    section {\n      background-color: #a7daff;\n    }\n\n    .note-icon {\n      background-color: var(--primary-color);\n    }\n  }\n}\n\n.note-icon {\n  position: absolute;\n  top: 0;\n  bottom: 0;\n  left: 0;\n  width: 80px;\n  padding-top: 20px;\n  display: flex;\n  justify-content: center;\n  background-color: var(--note-color);\n}\n\nmat-icon {\n  color: white;\n  font-size: 36px;\n  width: 36px;\n  height: 36px;\n  line-height: 36px;\n}\n\n.note-content {\n  margin-left: 80px;\n  padding: 0 1em;\n}\n\nsection {\n  min-height: 80px;\n  position: relative;\n  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);\n  margin: 4em 0;\n  display: flex;\n  align-items: center;\n  background-color: var(--note-bg);\n  border-radius: 0.5em;\n  overflow: hidden;\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/note/note.component.ts",
    "content": "import { Component } from '@angular/core';\nimport { MatIconModule } from '@angular/material/icon';\n\n@Component({\n  selector: 'note',\n  template: `\n    <section>\n        <span class=\"note-icon\">\n          <mat-icon>error_outline</mat-icon>\n        </span>\n      <div class=\"note-content\">\n        <ng-content></ng-content>\n      </div>\n    </section>\n  `,\n  styleUrl: './note.component.scss',\n  imports: [MatIconModule]\n})\nexport class NoteComponent {\n\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/pipes/keys.pipe.ts",
    "content": "import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n  name: 'keys',\n  standalone: true\n})\nexport class KeysPipe implements PipeTransform {\n  transform(value, args: string[]): any {\n    const keys = [];\n    for (const key in value) {\n      keys.push({ key: key, value: value[key] });\n    }\n    return keys;\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/section-title/section-title.component.scss",
    "content": ":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: 10px;\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/app/shared/section-title/section-title.component.ts",
    "content": "import { ChangeDetectionStrategy, Component } from '@angular/core';\nimport { faCaretRight } from '@fortawesome/free-solid-svg-icons';\nimport { FontAwesomeModule, IconDefinition } from '@fortawesome/angular-fontawesome';\n\n@Component({\n  selector: 'section-title',\n  template: `\n    <div fxLayout fxLayoutAlign=\"start center\">\n      <h2>\n        <i class=\"fas fa-caret-right\" aria-hidden=\"true\"></i>\n        <fa-icon [icon]=\"iconCaretRight\" size=\"lg\"></fa-icon>\n        <ng-content></ng-content>\n      </h2>\n    </div>\n  `,\n  styleUrl: 'section-title.component.scss',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  imports: [FontAwesomeModule]\n})\nexport class SectionTitleComponent {\n\n  iconCaretRight: IconDefinition = faCaretRight;\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n\n  <meta charset=\"utf-8\">\n  <title>Angular Gallery</title>\n  <base href=\"/\">\n\n  <meta\n    name=\"viewport\"\n    content=\"user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi\"\n  />\n\n  <link rel=\"icon\" type=\"image/x-icon\" href=\"icons/favicon.ico\">\n\n  <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"icons/apple-touch-icon.png\">\n  <link rel=\"icon\" type=\"image/png\" href=\"icons/favicon-32x32.png\" sizes=\"32x32\">\n  <link rel=\"icon\" type=\"image/png\" href=\"icons/favicon-16x16.png\" sizes=\"16x16\">\n  <link rel=\"manifest\" href=\"icons/site.webmanifest.json\">\n  <link rel=\"mask-icon\" href=\"icons/safari-pinned-tab.svg\" color=\"#5bbad5\">\n  <meta name=\"theme-color\" content=\"#ffffff\">\n\n  <meta name=\"title\" content=\"Angular Gallery\">\n  <meta name=\"description\"\n        content=\"Simplifies the process of creating beautiful image galleries for the web and mobile devices.\">\n\n  <meta name=\"twitter:card\" content=\"summary\">\n  <meta name=\"twitter:site\" content=\"@MurhafSousli\">\n  <meta name=\"twitter:creator\" content=\"@MurhafSousli\">\n\n  <meta property=\"og:site_name\" content=\"Angular Gallery\">\n  <meta property=\"og:title\" content=\"Angular Gallery\">\n  <meta property=\"og:description\"\n        content=\"Simplifies the process of creating beautiful image galleries for the web and mobile devices.\">\n  <meta property=\"og:url\" content=\"https://murhafsousli.github.io/ngx-gallery\">\n  <meta property=\"og:image\"\n        content=\"https://user-images.githubusercontent.com/8130692/35766040-0dc8027c-08e1-11e8-9567-cdfb4c4d0530.png\">\n\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n    <link\n      href=\"https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Roboto:wght@300;400;500;700&display=swap\"\n      rel=\"stylesheet\"\n    />\n    <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\">\n\n    <!-- Google tag (gtag.js) -->\n    <script async src=\"https://www.googletagmanager.com/gtag/js?id=G-69JERWT1EX\"></script>\n    <script>\n      window.dataLayer = window.dataLayer || [];\n      function gtag(){dataLayer.push(arguments);}\n      gtag('js', new Date());\n\n      gtag('config', 'G-69JERWT1EX');\n    </script>\n  </head>\n\n  <body class=\"mat-typography\">\n    <app-root></app-root>\n  </body>\n</html>\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/main.server.ts",
    "content": "import { bootstrapApplication } from '@angular/platform-browser';\nimport { AppComponent } from './app/app.component';\nimport { config } from './app/app.config.server';\n\nconst bootstrap = () => bootstrapApplication(AppComponent, config);\n\nexport default bootstrap;\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/main.ts",
    "content": "import { bootstrapApplication } from '@angular/platform-browser';\nimport { appConfig } from './app/app.config';\nimport { AppComponent } from './app/app.component';\n\nimport 'hammerjs';\n\nbootstrapApplication(AppComponent, appConfig)\n  .catch((err) => console.error(err));\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/mixin.scss",
    "content": "$mobile: 640px;\n\n@mixin mobile() {\n  @media (max-width: #{$mobile}) {\n    @content;\n  }\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/server.ts",
    "content": "import { APP_BASE_HREF } from '@angular/common';\nimport { CommonEngine, isMainModule } from '@angular/ssr/node';\nimport express from 'express';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport bootstrap from './main.server';\n\nconst serverDistFolder = dirname(fileURLToPath(import.meta.url));\nconst browserDistFolder = resolve(serverDistFolder, '../browser');\nconst indexHtml = join(serverDistFolder, 'index.server.html');\n\nconst app = express();\nconst commonEngine = new CommonEngine();\n\n/**\n * Example Express Rest API endpoints can be defined here.\n * Uncomment and define endpoints as necessary.\n *\n * Example:\n * ```ts\n * app.get('/api/**', (req, res) => {\n *   // Handle API request\n * });\n * ```\n */\n\n/**\n * Serve static files from /browser\n */\napp.get(\n  '**',\n  express.static(browserDistFolder, {\n    maxAge: '1y',\n    index: 'index.html'\n  }),\n);\n\n/**\n * Handle all other requests by rendering the Angular application.\n */\napp.get('**', (req, res, next) => {\n  const { protocol, originalUrl, baseUrl, headers } = req;\n\n  commonEngine\n    .render({\n      bootstrap,\n      documentFilePath: indexHtml,\n      url: `${protocol}://${headers.host}${originalUrl}`,\n      publicPath: browserDistFolder,\n      providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],\n    })\n    .then((html) => res.send(html))\n    .catch((err) => next(err));\n});\n\n/**\n * Start the server if this module is the main entry point.\n * The server listens on the port defined by the `PORT` environment variable, or defaults to 4000.\n */\nif (isMainModule(import.meta.url)) {\n  const port = process.env['PORT'] || 4000;\n  app.listen(port, () => {\n    console.log(`Node Express server listening on http://localhost:${port}`);\n  });\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/styles.scss",
    "content": "/* You can add global styles to this file, and also import other style files */\n@use '@angular/material' as mat;\n@use 'theme';\n@use 'mixin';\n\n* {\n  box-sizing: border-box;\n}\n\n:root {\n  --ng-progress-color: #E91E63;\n\n  --primary-color: #2196f3;\n  --accent-color: mat.m2-get-color-from-palette(mat.m2-define-palette(mat.$m2-pink-palette));\n  --danger-color: mat.m2-get-color-from-palette(mat.m2-define-palette(mat.$m2-yellow-palette, 300));\n\n  --text-color: #354c6d;// mat-color(mat-palette($mat-blue, 800));\n  --highlight-color: mat.m2-get-color-from-palette(mat.m2-define-palette(mat.$m2-blue-palette, 200));\n\n  --note-bg: mat.m2-get-color-from-palette(mat.m2-define-palette(mat.$m2-amber-palette, 100));\n  --note-color: mat.m2-get-color-from-palette(mat.m2-define-palette(mat.$m2-amber-palette, 600));\n\n  --code-color: var(--primary-color);\n  --code-bg-color: mat.m2-get-color-from-palette(mat.m2-define-palette(mat.$m2-blue-palette, 50));\n\n  --scrollbar-color: mat.m2-get-color-from-palette(mat.m2-define-palette(mat.$m2-yellow-palette));\n\n  --bg: #e8eff6;\n  --demo-gray-color: #E8EAF6;\n  --g-options-width: 300px;\n}\n\nbody {\n  font-family: Roboto, Helvetica Neue Light, Helvetica Neue, Helvetica, Arial, Lucida Grande, sans-serif;\n  margin: 0;\n  height: 100vh;\n  overflow: hidden;\n  --header-height: 56px;\n}\n\np {\n  line-height: 2em;\n}\n\nh3 {\n  margin-top: 3em;\n}\n\n.container {\n  max-width: 900px;\n  margin: 0 auto;\n  padding: 0 1em;\n}\n\n.install {\n  margin: 3em 0;\n}\n\nmat-sidenav-container {\n  height: 100%;\n}\n\n.page {\n  position: absolute;\n  left: 0;\n  right: 0;\n  top: var(--header-height);\n  bottom: 0;\n  overflow: auto;\n  transform: perspective(1000px);\n  backface-visibility: hidden;\n  transform-style: preserve-3d;\n}\n\n.mdc-button {\n  letter-spacing: 1px;\n  font-size: 14px;\n}\n\nsection {\n  margin-bottom: 2em;\n  display: block;\n  width: 100%;\n}\n\ncode {\n  flex: 1;\n  box-sizing: border-box;\n  font-weight: normal;\n  line-height: 1.75em;\n  margin: 0 0.3em;\n  display: inline-block !important;\n  padding: 0 6px !important;\n  overflow-x: unset !important;\n  background-color: white !important;\n  border-radius: 3px;\n  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);\n  font-size: 14px;\n}\n\n.hljs {\n  color: var(--text-color);\n}\n\na {\n  text-decoration: none;\n  color: var(--accent-color);\n}\n\n.usage-title {\n  display: flex;\n\n  p {\n    margin: 0;\n  }\n}\n\n.section-number {\n  flex: 0 0 auto;\n  border-radius: 50%;\n  color: var(--primary-color);\n  box-shadow: 0 3px 10px rgb(0 0 0 / 10%);\n  width: 30px;\n  height: 30px;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  margin-right: 1em;\n  background: white;\n}\n\n.inline-number {\n  display: inline-flex;\n  margin: 0 0.2em;\n}\n\n.page-title {\n  box-shadow: inset 0 -3px 8px 0 rgba(0, 0, 0, 0.08);\n  background-color: var(--primary-color);\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n  color: #fff;\n\n  h1 {\n    letter-spacing: 2px;\n    text-align: center;\n    margin: 2em auto;\n    padding: 16px;\n    font-weight: normal;\n  }\n}\n\n.page-content {\n  position: relative;\n  @extend .container;\n}\n\n// code\n.hljs-attr {\n  color: var(--primary-color);\n}\n\n// Used in for features in Home page and Gallerize page\n\n.row {\n  display: flex;\n  justify-content: center;\n  flex-wrap: wrap;\n  padding: 0 4px;\n}\n\n/* Create four equal columns that sits next to each other */\n.column {\n  flex: 25%;\n  padding: 0 4px;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n}\n\n.column img {\n  margin-top: 8px;\n  vertical-align: middle;\n}\n\n.separator {\n  height: 120px;\n  width: 20%;\n  background-color: rgba(var(--primary-color), 0.2);\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/src/theme.scss",
    "content": "@use '@angular/material' as mat;\n\n@include mat.core();\n\n//$demo-primary: mat-palette($mat-deep-purple, 900); //$mat-indigo\n$demo-primary: mat.m2-define-palette(mat.$m2-blue-palette);\n$demo-accent: mat.m2-define-palette(mat.$m2-pink-palette, 400);\n$demo-warn: mat.m2-define-palette(mat.$m2-amber-palette, 300);\n\n$demo-theme: mat.m2-define-light-theme((\n  color: (\n    primary: $demo-primary,\n    accent: $demo-accent,\n    warn: $demo-warn,\n  ),\n  typography: mat.m2-define-typography-config(),\n  density: 0,\n));\n\n@include mat.all-component-themes($demo-theme);\n\n"
  },
  {
    "path": "projects/ng-gallery-demo/tsconfig.app.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../out-tsc/app\",\n    \"types\": [\n      \"node\"\n    ]\n  },\n  \"files\": [\n    \"src/main.ts\",\n    \"src/main.server.ts\",\n    \"src/server.ts\"\n  ],\n  \"include\": [\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "projects/ng-gallery-demo/tsconfig.spec.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"../../out-tsc/spec\",\n    \"types\": [\n      \"jasmine\"\n    ]\n  },\n  \"include\": [\n    \"src/**/*.spec.ts\",\n    \"src/**/*.d.ts\"\n  ]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "/* To learn more about this file see: https://angular.io/config/tsconfig. */\n{\n  \"compileOnSave\": false,\n  \"compilerOptions\": {\n    \"outDir\": \"./dist/out-tsc\",\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": false,\n    \"noImplicitOverride\": true,\n    \"noPropertyAccessFromIndexSignature\": false,\n    \"noImplicitReturns\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"ng-gallery\": [\n        \"./projects/ng-gallery/src/public-api\"\n      ],\n      \"ng-gallery/lightbox\": [\n        \"./projects/ng-gallery/lightbox/src/public_api\"\n      ]\n    },\n    \"esModuleInterop\": true,\n    \"sourceMap\": true,\n    \"declaration\": false,\n    \"experimentalDecorators\": true,\n    \"moduleResolution\": \"node\",\n    \"importHelpers\": true,\n    \"target\": \"ES2022\",\n    \"module\": \"ES2022\",\n    \"useDefineForClassFields\": false,\n    \"lib\": [\n      \"ES2022\",\n      \"dom\"\n    ]\n  },\n  \"angularCompilerOptions\": {\n    \"enableI18nLegacyMessageIdFormat\": false,\n    \"strictInjectionParameters\": true,\n    \"strictInputAccessModifiers\": true,\n    \"strictTemplates\": false\n  }\n}\n"
  }
]