├── .all-contributorsrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── issue-management.yml │ ├── nodejs.yml │ └── npm-publish.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── apps ├── ngu-carousel-e2e │ ├── .eslintrc.json │ ├── cypress.config.ts │ ├── project.json │ ├── src │ │ ├── e2e │ │ │ └── app.cy.ts │ │ ├── fixtures │ │ │ └── example.json │ │ └── support │ │ │ ├── app.po.ts │ │ │ ├── commands.ts │ │ │ └── e2e.ts │ └── tsconfig.json └── ngu-carousel-example │ ├── .eslintrc.json │ ├── karma.conf.js │ ├── project.json │ ├── server.ts │ ├── src │ ├── _redirects │ ├── app │ │ ├── app-server.config.ts │ │ ├── app.component.html │ │ ├── app.component.scss │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.config.ts │ │ ├── app.routes.ts │ │ ├── banner-vertical │ │ │ ├── banner-vertical.component.html │ │ │ ├── banner-vertical.component.scss │ │ │ ├── banner-vertical.component.spec.ts │ │ │ └── banner-vertical.component.ts │ │ ├── banner │ │ │ ├── banner.component.html │ │ │ ├── banner.component.scss │ │ │ ├── banner.component.spec.ts │ │ │ └── banner.component.ts │ │ ├── getting-started │ │ │ ├── getting-started.component.html │ │ │ ├── getting-started.component.scss │ │ │ ├── getting-started.component.spec.ts │ │ │ └── getting-started.component.ts │ │ ├── main-nav │ │ │ ├── main-nav.component.html │ │ │ ├── main-nav.component.scss │ │ │ ├── main-nav.component.spec.ts │ │ │ └── main-nav.component.ts │ │ ├── slide-animation.ts │ │ ├── tile-2-images │ │ │ ├── tile-2-images.component.css │ │ │ ├── tile-2-images.component.html │ │ │ ├── tile-2-images.component.spec.ts │ │ │ └── tile-2-images.component.ts │ │ ├── tile │ │ │ ├── tile.component.html │ │ │ ├── tile.component.scss │ │ │ ├── tile.component.spec.ts │ │ │ └── tile.component.ts │ │ └── wrapped │ │ │ ├── wrapped-carousel │ │ │ ├── wrapped-carousel.component.html │ │ │ ├── wrapped-carousel.component.scss │ │ │ └── wrapped-carousel.component.ts │ │ │ └── wrapped.component.ts │ ├── assets │ │ ├── .gitkeep │ │ ├── bg.jpg │ │ ├── canberra.jpg │ │ ├── car.png │ │ ├── holi.jpg │ │ └── mock │ │ │ └── images.json │ ├── favicon.ico │ ├── index.html │ ├── main.server.ts │ ├── main.ts │ ├── styles.scss │ └── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ └── tsconfig.spec.json ├── decorate-angular-cli.js ├── karma.conf.js ├── libs └── ngu │ └── carousel │ ├── .eslintrc.json │ ├── .storybook │ ├── main.js │ ├── preview.js │ └── tsconfig.json │ ├── README.md │ ├── cypress.config.ts │ ├── cypress │ ├── fixtures │ │ └── example.json │ ├── support │ │ ├── commands.ts │ │ ├── component-index.html │ │ └── component.ts │ └── tsconfig.json │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── src │ ├── lib │ │ ├── carousel-animation.ts │ │ ├── ngu-carousel.directive.ts │ │ ├── ngu-carousel │ │ │ ├── ngu-carousel-hammer-manager.ts │ │ │ ├── ngu-carousel.component.html │ │ │ ├── ngu-carousel.component.scss │ │ │ ├── ngu-carousel.component.ts │ │ │ ├── ngu-carousel.ts │ │ │ └── ngu-window-scroll-listener.ts │ │ ├── ngu-item │ │ │ ├── ngu-item.component.html │ │ │ ├── ngu-item.component.scss │ │ │ ├── ngu-item.component.stories.ts │ │ │ └── ngu-item.component.ts │ │ ├── ngu-tile │ │ │ ├── ngu-tile.component.html │ │ │ ├── ngu-tile.component.scss │ │ │ ├── ngu-tile.component.stories.ts │ │ │ └── ngu-tile.component.ts │ │ └── symbols.ts │ ├── public-api.ts │ └── test.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ └── tsconfig.spec.json ├── lint-staged.config.mjs ├── nx.json ├── package.json ├── pnpm-lock.yaml ├── renovate.json ├── scripts └── get-os.js ├── storybook-migration-summary.md ├── tools └── tsconfig.tools.json └── tsconfig.base.json /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "codeveins", 10 | "name": "Code Veins", 11 | "avatar_url": "https://avatars0.githubusercontent.com/u/47743769?v=4", 12 | "profile": "https://github.com/codeveins", 13 | "contributions": [ 14 | "code" 15 | ] 16 | }, 17 | { 18 | "login": "mczachurski", 19 | "name": "Marcin Czachurski", 20 | "avatar_url": "https://avatars0.githubusercontent.com/u/6191974?v=4", 21 | "profile": "https://github.com/mczachurski", 22 | "contributions": [ 23 | "code" 24 | ] 25 | }, 26 | { 27 | "login": "reed665", 28 | "name": "Alexander Buiko", 29 | "avatar_url": "https://avatars2.githubusercontent.com/u/4117678?v=4", 30 | "profile": "https://www.linkedin.com/in/alexander-buiko-16715282/", 31 | "contributions": [ 32 | "code" 33 | ] 34 | }, 35 | { 36 | "login": "fridolin-koch", 37 | "name": "Frido Koch", 38 | "avatar_url": "https://avatars2.githubusercontent.com/u/1880828?v=4", 39 | "profile": "https://fkse.io", 40 | "contributions": [ 41 | "code" 42 | ] 43 | }, 44 | { 45 | "login": "gimox", 46 | "name": "Giorgio Modoni", 47 | "avatar_url": "https://avatars3.githubusercontent.com/u/2871248?v=4", 48 | "profile": "https://github.com/gimox", 49 | "contributions": [ 50 | "code" 51 | ] 52 | }, 53 | { 54 | "login": "faran312", 55 | "name": "faran312", 56 | "avatar_url": "https://avatars1.githubusercontent.com/u/27755518?v=4", 57 | "profile": "https://github.com/faran312", 58 | "contributions": [ 59 | "code" 60 | ] 61 | }, 62 | { 63 | "login": "euangoddard", 64 | "name": "Euan Goddard", 65 | "avatar_url": "https://avatars1.githubusercontent.com/u/412672?v=4", 66 | "profile": "https://www.stockopedia.com/", 67 | "contributions": [ 68 | "code" 69 | ] 70 | }, 71 | { 72 | "login": "santoshyadav198613", 73 | "name": "Santosh Yadav", 74 | "avatar_url": "https://avatars3.githubusercontent.com/u/11923975?v=4", 75 | "profile": "https://www.santoshyadav.dev", 76 | "contributions": [ 77 | "code" 78 | ] 79 | }, 80 | { 81 | "login": "tiagomsmagalhaes", 82 | "name": "Tiago Magalhães", 83 | "avatar_url": "https://avatars1.githubusercontent.com/u/5302040?v=4", 84 | "profile": "http://tiagomagalha.es", 85 | "contributions": [ 86 | "doc" 87 | ] 88 | }, 89 | { 90 | "login": "samvloeberghs", 91 | "name": "Sam Vloeberghs", 92 | "avatar_url": "https://avatars3.githubusercontent.com/u/231618?v=4", 93 | "profile": "https://samvloeberghs.be", 94 | "contributions": [ 95 | "doc" 96 | ] 97 | }, 98 | { 99 | "login": "rat-matheson", 100 | "name": "rat-matheson", 101 | "avatar_url": "https://avatars0.githubusercontent.com/u/61026434?v=4", 102 | "profile": "https://github.com/rat-matheson", 103 | "contributions": [ 104 | "code" 105 | ] 106 | }, 107 | { 108 | "login": "Yberion", 109 | "name": "Brandon Largeau", 110 | "avatar_url": "https://avatars.githubusercontent.com/u/4186385?v=4", 111 | "profile": "https://github.com/Yberion", 112 | "contributions": [ 113 | "code" 114 | ] 115 | }, 116 | { 117 | "login": "arturovt", 118 | "name": "Artur Androsovych", 119 | "avatar_url": "https://avatars.githubusercontent.com/u/7337691?v=4", 120 | "profile": "http://ng-guru.io", 121 | "contributions": [ 122 | "code" 123 | ] 124 | }, 125 | { 126 | "login": "chivesrs", 127 | "name": "chivesrs", 128 | "avatar_url": "https://avatars.githubusercontent.com/u/7110657?v=4", 129 | "profile": "https://github.com/chivesrs", 130 | "contributions": [ 131 | "code" 132 | ] 133 | }, 134 | { 135 | "login": "luca-peruzzo", 136 | "name": "Luca Peruzzo", 137 | "avatar_url": "https://avatars.githubusercontent.com/u/69015314?v=4", 138 | "profile": "https://github.com/luca-peruzzo", 139 | "contributions": [ 140 | "code" 141 | ] 142 | }, 143 | { 144 | "login": "va-stefanek", 145 | "name": "Mateusz Stefańczyk", 146 | "avatar_url": "https://avatars.githubusercontent.com/u/67691339?v=4", 147 | "profile": "https://houseofangular.io/", 148 | "contributions": [ 149 | "code" 150 | ] 151 | }, 152 | { 153 | "login": "JeanMeche", 154 | "name": "Matthieu Riegler", 155 | "avatar_url": "https://avatars.githubusercontent.com/u/1300985?v=4", 156 | "profile": "http://riegler.fr", 157 | "contributions": [ 158 | "review" 159 | ] 160 | }, 161 | { 162 | "login": "sasidharansd", 163 | "name": "Sasidharan SD", 164 | "avatar_url": "https://avatars.githubusercontent.com/u/47988127?v=4", 165 | "profile": "https://github.com/sasidharansd", 166 | "contributions": [ 167 | "doc" 168 | ] 169 | }, 170 | { 171 | "login": "jonplata", 172 | "name": "Jonatan Plata", 173 | "avatar_url": "https://avatars.githubusercontent.com/u/14810139?v=4", 174 | "profile": "https://github.com/jonplata", 175 | "contributions": [ 176 | "code" 177 | ] 178 | } 179 | ], 180 | "contributorsPerLine": 7, 181 | "projectName": "ngu-carousel", 182 | "projectOwner": "uiuniversal", 183 | "repoType": "github", 184 | "repoHost": "https://github.com", 185 | "skipCi": true, 186 | "commitConvention": "angular", 187 | "commitType": "docs" 188 | } 189 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parserOptions": { 4 | "ecmaVersion": 2020 5 | }, 6 | "ignorePatterns": ["**/*"], 7 | "overrides": [ 8 | { 9 | "files": ["*.ts"], 10 | "parserOptions": { 11 | "createDefaultProgram": true 12 | }, 13 | "extends": [ 14 | "plugin:@angular-eslint/recommended", 15 | "plugin:@angular-eslint/template/process-inline-templates" 16 | ], 17 | "rules": { 18 | "@angular-eslint/component-selector": [ 19 | "error", 20 | { 21 | "prefix": "app", 22 | "style": "kebab-case", 23 | "type": "element" 24 | } 25 | ], 26 | "@angular-eslint/directive-selector": [ 27 | "error", 28 | { 29 | "prefix": "app", 30 | "style": "camelCase", 31 | "type": "attribute" 32 | } 33 | ] 34 | } 35 | }, 36 | { 37 | "files": ["*.html"], 38 | "extends": ["plugin:@angular-eslint/template/recommended"], 39 | "rules": {} 40 | }, 41 | { 42 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 43 | "rules": { 44 | "@nx/enforce-module-boundaries": [ 45 | "error", 46 | { 47 | "enforceBuildableLibDependency": true, 48 | "allow": [], 49 | "depConstraints": [ 50 | { 51 | "sourceTag": "*", 52 | "onlyDependOnLibsWithTags": ["*"] 53 | } 54 | ] 55 | } 56 | ] 57 | } 58 | } 59 | ], 60 | "plugins": ["@nx"], 61 | "extends": ["plugin:storybook/recommended"] 62 | } 63 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | ** Stackblitz ** 24 | Provide a repo using [Angular Stackblitz](https://stackblitz.com/edit/angular) 25 | If not provided the issue may be closed. 26 | 27 | **Desktop (please complete the following information):** 28 | 29 | - OS: [e.g. iOS] 30 | - Browser [e.g. chrome, safari] 31 | - Version [e.g. 22] 32 | 33 | **Smartphone (please complete the following information):** 34 | 35 | - Device: [e.g. iPhone6] 36 | - OS: [e.g. iOS8.1] 37 | - Browser [e.g. stock browser, safari] 38 | - Version [e.g. 22] 39 | 40 | **Additional context** 41 | Version of 42 | Angular: 43 | carousel: 44 | 45 | Would you like to open a PR (Support will be provided): 46 | [ ] Yes 47 | [ ] No 48 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: Feature request 6 | assignees: '' 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | 21 | Would you like to open a PR (Support will be provided): 22 | [ ] Yes 23 | [ ] No 24 | -------------------------------------------------------------------------------- /.github/workflows/issue-management.yml: -------------------------------------------------------------------------------- 1 | name: 'Close Stale Issues' 2 | 3 | on: 4 | schedule: 5 | - cron: '0 10 * * 5' # Scheduled to run every Friday at 12pm CET 6 | 7 | jobs: 8 | stale-issue-handler: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Mark and close stale issues 13 | uses: actions/stale@v9 14 | with: 15 | days-before-stale: 180 16 | days-before-close: 0 17 | stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity for 6 months. It will be closed if no further activity occurs. Thank you for your contributions.' 18 | close-issue-message: 'Closing this issue due to no activity for 6 months.' 19 | stale-pr-message: 'This PR has been automatically marked as stale because it has not had recent activity for 6 months. It will be closed if no further activity occurs. Thank you for your contributions.' 20 | close-pr-message: 'Closing this PR due to no activity for 6 months.' 21 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [master] 9 | pull_request: 10 | branches: [master] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: ['20', '18.19.1'] 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Install pnpm 23 | uses: pnpm/action-setup@v4 24 | with: 25 | version: 9 26 | - name: Use Node.js ${{ matrix.node-version }} 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | - name: Install dependencies 31 | run: pnpm install 32 | - name: Build 33 | run: | 34 | pnpx nx run-many --target=build --all 35 | env: 36 | CI: true 37 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | push: 8 | branches: 9 | - 'release/**' 10 | release: 11 | types: [created] 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Install pnpm 19 | uses: pnpm/action-setup@v4 20 | with: 21 | version: 9 22 | - uses: actions/setup-node@v4 23 | with: 24 | node-version: 18.13.0 25 | - run: pnpm install 26 | 27 | publish-npm: 28 | needs: build 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v4 32 | - name: Install pnpm 33 | uses: pnpm/action-setup@v4 34 | with: 35 | version: 9 36 | - uses: actions/setup-node@v4 37 | with: 38 | node-version: 18.13.0 39 | registry-url: https://registry.npmjs.org/ 40 | scope: '@ngu' 41 | - name: Build and Publish 42 | run: | 43 | pnpm install 44 | pnpx nx run-many --target=build --all 45 | cd dist/libs/ngu/carousel 46 | npm publish --access=public 47 | shell: bash 48 | env: 49 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 50 | 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | 16 | # IDEs and editors 17 | /.idea 18 | .project 19 | .classpath 20 | .c9/ 21 | *.launch 22 | .settings/ 23 | *.sublime-workspace 24 | 25 | # IDE - VSCode 26 | .vscode/* 27 | !.vscode/settings.json 28 | !.vscode/tasks.json 29 | !.vscode/launch.json 30 | !.vscode/extensions.json 31 | .history/* 32 | 33 | # misc 34 | .angular 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | **/reports 44 | .nx 45 | 46 | # System Files 47 | .DS_Store 48 | Thumbs.db 49 | migrations.json 50 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | *.yml 3 | 4 | .angular 5 | 6 | /.nx/cache 7 | /.nx/workspace-data 8 | 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "none", 4 | "printWidth": 100, 5 | "useTabs": false, 6 | "tabWidth": 2, 7 | "bracketSpacing": true, 8 | "semi": true, 9 | "arrowParens": "avoid", 10 | "htmlWhitespaceSensitivity": "strict" 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ghaschel.vscode-angular-html", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode", 6 | "nrwl.angular-console", 7 | "angular.ng-template" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.singleQuote": true, 3 | "editor.formatOnSave": true 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ngu-carousel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ngu-carousel 2 | 3 | [![npm downloads](https://img.shields.io/npm/dt/@ngu/carousel?label=npm%20downloads)](https://www.npmjs.com/package/@ngu/carousel) 4 | 5 | [![npm version](https://badge.fury.io/js/%40ngu%2Fcarousel.svg)](https://badge.fury.io/js/%40ngu%2Fcarousel) 6 | 7 | 8 | [![All Contributors](https://img.shields.io/badge/all_contributors-19-orange.svg?style=flat-square)](#contributors-) 9 | 10 | 11 | Angular Universal carousel 12 | 13 | `Note: This carousel doesn't include any CSS. go and customize CSS for buttons, items except ngucarousel and ngucarousel-inner` 14 | 15 | ## Demo 16 | 17 | Demo available in Stackblitz [Here](https://stackblitz.com/edit/ngu-carousel-ng6) 18 | 19 | Demo available [Here](https://ngu-carousel.netlify.app) 20 | 21 | ## Installation 22 | 23 | `ngu-carousel` supports touch actions and requires `hammerjs` to be installed before the `ngu-carousel` is installed. 24 | 25 | | Angular Version | ngu-carousel Version | 26 | | ------------------------ | ---------------------------------- | 27 | | Angular >= 19 | `npm i --save @ngu/carousel@19` | 28 | | Angular >= 18 | `npm i --save @ngu/carousel@18` | 29 | | Angular >= 17 | `npm i --save @ngu/carousel@9.0.0` | 30 | | Angular >= 16 standalone | `npm i --save @ngu/carousel@8.0.0` | 31 | | Angular >= 16 | `npm i --save @ngu/carousel@7.2.0` | 32 | | Angular >= 15 | `npm i --save @ngu/carousel@7.0.0` | 33 | | Angular >= 14 | `npm i --save @ngu/carousel@6.0.0` | 34 | | Angular >= 13 | `npm i --save @ngu/carousel@5.0.0` | 35 | | Angular >= 12 | `npm i --save @ngu/carousel@4.0.0` | 36 | | Angular >= 10 | `npm i --save @ngu/carousel@3.0.2` | 37 | | Angular = 9 | `npm i --save @ngu/carousel@2.1.0` | 38 | | Angular < 9 | `npm i --save @ngu/carousel@1.5.5` | 39 | 40 | ## Usage 41 | 42 | 1. Include Carousel needed parts in your module or component (all carousel components and directives are standalone): 43 | 44 | ```typescript 45 | import { 46 | NguCarousel, 47 | NguCarouselDefDirective, 48 | NguCarouselNextDirective, 49 | NguCarouselPrevDirective, 50 | NguItemComponent 51 | } from '@ngu/carousel'; 52 | 53 | @NgModule({ 54 | imports: [ 55 | NguCarousel, 56 | NguTileComponent, 57 | NguCarousel, 58 | NguCarouselDefDirective, 59 | NguCarouselNextDirective, 60 | NguCarouselPrevDirective, 61 | NguItemComponent 62 | ] 63 | }) 64 | export class AppModule {} 65 | 66 | OR; 67 | 68 | @Component({ 69 | imports: [ 70 | NguCarousel, 71 | NguTileComponent, 72 | NguCarousel, 73 | NguCarouselDefDirective, 74 | NguCarouselNextDirective, 75 | NguCarouselPrevDirective, 76 | NguItemComponent 77 | ], 78 | standalone: true 79 | }) 80 | export class AppComponent {} 81 | ``` 82 | 83 | 2. Then use in your component: 84 | 85 | ```javascript 86 | import { Component, OnInit } from '@angular/core'; 87 | import { NguCarouselConfig } from '@ngu/carousel'; 88 | 89 | @Component({ 90 | selector: 'sample', 91 | template: ` 92 | 93 | 94 | 95 | 96 | 97 |
98 |

{{j}}

99 |
100 |
101 | 102 | 103 |
    104 |
  • 106 |
107 |
108 | 109 |
110 | 111 | 112 | 116 |
117 | 118 | `, 119 | }) 120 | export class SampleComponent implements OnInit { 121 | imgags = [ 122 | 'assets/bg.jpg', 123 | 'assets/car.png', 124 | 'assets/canberra.jpg', 125 | 'assets/holi.jpg' 126 | ]; 127 | public carouselTileItems: Array = [0, 1, 2, 3, 4, 5]; 128 | public carouselTiles = { 129 | 0: [], 130 | 1: [], 131 | 2: [], 132 | 3: [], 133 | 4: [], 134 | 5: [] 135 | }; 136 | public carouselTile: NguCarouselConfig = { 137 | grid: { xs: 1, sm: 1, md: 3, lg: 3, all: 0 }, 138 | slide: 3, 139 | speed: 250, 140 | point: { 141 | visible: true 142 | }, 143 | load: 2, 144 | velocity: 0, 145 | touch: true, 146 | easing: 'cubic-bezier(0, 0, 0.2, 1)' 147 | }; 148 | constructor() {} 149 | 150 | ngOnInit() { 151 | this.carouselTileItems.forEach(el => { 152 | this.carouselTileLoad(el); 153 | }); 154 | } 155 | 156 | public carouselTileLoad(j) { 157 | // console.log(this.carouselTiles[j]); 158 | const len = this.carouselTiles[j].length; 159 | if (len <= 30) { 160 | for (let i = len; i < len + 15; i++) { 161 | this.carouselTiles[j].push( 162 | this.imgags[Math.floor(Math.random() * this.imgags.length)] 163 | ); 164 | } 165 | } 166 | } 167 | } 168 | ``` 169 | 170 | ## Input Interface 171 | 172 | ```javascript 173 | export class NguCarouselStore { 174 | type: string; 175 | deviceType: DeviceType; 176 | token: string; 177 | items: number; 178 | load: number; 179 | deviceWidth: number; 180 | carouselWidth: number; 181 | itemWidth: number; 182 | visibleItems: ItemsControl; 183 | slideItems: number; 184 | itemWidthPer: number; 185 | itemLength: number; 186 | currentSlide: number; 187 | easing: string; 188 | speed: number; 189 | transform: Transfrom; 190 | loop: boolean; 191 | dexVal: number; 192 | touchTransform: number; 193 | touch: Touch; 194 | isEnd: boolean; 195 | isFirst: boolean; 196 | isLast: boolean; 197 | RTL: boolean; 198 | vertical: Vertical; 199 | } 200 | export type DeviceType = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'all'; 201 | 202 | export class ItemsControl { 203 | start: number; 204 | end: number; 205 | } 206 | 207 | export class Vertical { 208 | enabled: boolean; 209 | height: number; 210 | } 211 | 212 | export class Touch { 213 | active?: boolean; 214 | swipe: string; 215 | velocity: number; 216 | } 217 | 218 | export class NguCarouselConfig { 219 | grid: Transfrom; 220 | gridBreakpoints?: Breakpoints; 221 | slide?: number; 222 | speed?: number; 223 | interval?: CarouselInterval; 224 | animation?: Animate; 225 | point?: Point; 226 | type?: string; 227 | load?: number; 228 | custom?: Custom; 229 | loop?: boolean; 230 | touch?: boolean; 231 | easing?: string; 232 | RTL?: boolean; 233 | button?: NguButton; 234 | vertical?: Vertical; 235 | velocity?: number; 236 | } 237 | 238 | export class Grid { 239 | xs: number; 240 | sm: number; 241 | md: number; 242 | lg: number; 243 | xl: number; 244 | all: number; 245 | } 246 | 247 | export interface Point { 248 | visible: boolean; 249 | hideOnSingleSlide?: boolean; 250 | } 251 | 252 | export type Custom = 'banner'; 253 | export type Animate = 'lazy'; 254 | ``` 255 | 256 | | Command | Type | Required | Description | 257 | | ------------------------- | ------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 258 | | `grid` | Object | Yes | **xs** - mobile, **sm** - tablet, **md** - desktop, **lg** - large desktops, **xl** - extra large desktops, **all** - fixed width (When you use **all** make others 0 and vice versa) | 259 | | `gridBreakpoints` | Object | optional | Determines the browser width in pixels that the grid displays the intended number of tiles.

default: `{sm: 768, md: 992, lg: 1200, xl: 1200}` | 260 | | `slide` | number | optional | It is used to slide the number items on click | 261 | | `speed` | milliseconds | optional | It is used for time taken to slide the number items | 262 | | `interval` | milliseconds | optional | It is used to make the carousel auto slide with given value. interval defines the interval between slides | 263 | | `load` | number | optional | It is used to load the items similar to pagination. The carousel will trigger the carouselLoad function to load another set of items. It will help you to improve the performance of the app.**`(carouselLoad)="myfunc($event)"`** | 264 | | `point.visible` | boolean | optional | It is used to indicate no. of slides and also shows the current active slide. | 265 | | `point.hideOnSingleSlide` | boolean | optional | It is used to hide the point indicator when slide is less than one. | 266 | | `touch` | boolean | optional | It is used to active touch support to the carousel. | 267 | | `easing` | string | optional | It is used to define the easing style of the carousel. Only define the ease name without any timing like `ease`,`ease-in` | 268 | | `loop` | boolean | optional | It is used to loop the `ngu-item ngu-tile`. It must be true for `interval` | 269 | | `animation` | string | optional | It is used to animate the sliding items. currently it only supports `lazy`. more coming soon and also with custom CSS animation option | 270 | | `custom` | string | optional | It is you to define the purpose of the carousel. Currently, it only supports `banner`. | 271 | | `RTL` | boolean | optional | This option enables the `rtl` direction and acts as rtl. By default it is set to `ltr` | 272 | | `vertical.enabled` | boolean | optional | This option enable the `vertical` direction | 273 | | `vertical.height` | number | optional | This option is used to set the height of the carousel | 274 | 275 | ### Custom CSS for Point 276 | 277 | ```html 278 | 281 | ``` 282 | 283 | This is HTML I'm using in the carousel. Add your own CSS according to this elements in `pointStyles`. check below guide for more details. 284 | 285 | ```html 286 | 287 | 288 | ``` 289 | 290 | - `inputs` is an `Input` and It accepts `NguCarouselConfig`. 291 | - `onMove` is an `Output` which triggered on every slide before start and it will emit `$event` as `NguCarouselStore` object. 292 | - `carouselLoad` is an `Output` which triggered when slide reaches the end on items based on inputs `load`. 293 | 294 | # Getstarted guide 295 | 296 | ## Banner with Custom point style 297 | 298 | ```javascript 299 | import { Component } from '@angular/core'; 300 | import { NguCarousel, NguCarouselStore } from '@ngu/carousel'; 301 | 302 | @Component({ 303 | selector: 'app-carousel', 304 | template: ` 305 | 306 | 307 |

1

308 |
309 | 310 | 311 |

2

312 |
313 | 314 | 315 |

3

316 |
317 | 318 | 319 | 320 |
321 | `, 322 | styles: [ 323 | ` 324 | .bannerStyle h1 { 325 | background-color: #ccc; 326 | min-height: 300px; 327 | text-align: center; 328 | line-height: 300px; 329 | } 330 | .leftRs { 331 | position: absolute; 332 | margin: auto; 333 | top: 0; 334 | bottom: 0; 335 | width: 50px; 336 | height: 50px; 337 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 338 | border-radius: 999px; 339 | left: 0; 340 | } 341 | 342 | .rightRs { 343 | position: absolute; 344 | margin: auto; 345 | top: 0; 346 | bottom: 0; 347 | width: 50px; 348 | height: 50px; 349 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 350 | border-radius: 999px; 351 | right: 0; 352 | } 353 | ` 354 | ] 355 | }) 356 | export class Sample implements OnInit { 357 | ngOnInit() { 358 | this.carouselBanner = { 359 | grid: { xs: 1, sm: 1, md: 1, lg: 1, xl: 1, all: 0 }, 360 | slide: 1, 361 | speed: 400, 362 | interval: { 363 | timing: 3000, 364 | initialDelay: 1000 365 | }, 366 | point: { 367 | visible: true 368 | }, 369 | load: 2, 370 | loop: true, 371 | touch: true 372 | }; 373 | } 374 | 375 | /* It will be triggered on every slide*/ 376 | onmoveFn(data: NguCarouselStore) { 377 | console.log(data); 378 | } 379 | 380 | trackCarousel(_, item) { 381 | return item; 382 | } 383 | } 384 | ``` 385 | 386 | ## Banner with Vertical carousel 387 | 388 | ```javascript 389 | import { Component } from '@angular/core'; 390 | import { NguCarousel, NguCarouselConfig } from '@ngu/carousel'; 391 | 392 | @Component({ 393 | selector: 'app-carousel', 394 | template: ` 395 | 398 | 399 | 400 |

1

401 |
402 | 403 | 404 |

2

405 |
406 | 407 | 408 |

3

409 |
410 | 411 | 412 | 413 |
414 | `, 415 | styles: [ 416 | ` 417 | .bannerStyle h1 { 418 | background-color: #ccc; 419 | min-height: 300px; 420 | text-align: center; 421 | line-height: 300px; 422 | } 423 | .leftRs { 424 | position: absolute; 425 | margin: auto; 426 | top: 0; 427 | bottom: 0; 428 | width: 50px; 429 | height: 50px; 430 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, .3); 431 | border-radius: 999px; 432 | left: 0; 433 | } 434 | 435 | .rightRs { 436 | position: absolute; 437 | margin: auto; 438 | top: 0; 439 | bottom: 0; 440 | width: 50px; 441 | height: 50px; 442 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, .3); 443 | border-radius: 999px; 444 | right: 0; 445 | } 446 | 447 | .ngucarouselPoint { 448 | list-style-type: none; 449 | text-align: center; 450 | padding: 12px; 451 | margin: 0; 452 | white-space: nowrap; 453 | overflow: auto; 454 | position: absolute; 455 | width: 100%; 456 | bottom: 20px; 457 | left: 0; 458 | box-sizing: border-box; 459 | } 460 | .ngucarouselPoint li { 461 | display: inline-block; 462 | border-radius: 999px; 463 | background: rgba(255, 255, 255, 0.55); 464 | padding: 5px; 465 | margin: 0 3px; 466 | transition: .4s ease all; 467 | } 468 | .ngucarouselPoint li.active { 469 | background: white; 470 | width: 10px; 471 | } 472 | ` 473 | ] 474 | }) 475 | export class Sample implements OnInit { 476 | ngOnInit() { 477 | this.carouselBanner = { 478 | grid: { xs: 1, sm: 1, md: 1, lg: 1, xl:1, all: 0 }, 479 | slide: 1, 480 | speed: 400, 481 | interval: 4000, 482 | point: { 483 | visible: true 484 | }, 485 | load: 2, 486 | loop: true, 487 | touch: true, // touch is not currently in active for vertical carousel, will enable it in future build 488 | vertical { 489 | enabled: true, 490 | height: 400 491 | } 492 | }; 493 | } 494 | 495 | /* It will be triggered on every slide*/ 496 | onmoveFn(data: NguCarousel) { 497 | console.log(data); 498 | } 499 | } 500 | ``` 501 | 502 | ## Tile with Carousel Control 503 | 504 | ```javascript 505 | import { Component } from '@angular/core'; 506 | import { NguCarousel, NguCarouselConfig } from '@ngu/carousel'; 507 | 508 | @Component({ 509 | selector: 'app-carousel', 510 | template: ` 511 | 514 | 515 | 516 |

{{Tile + 1}}

517 |
518 | 519 | 520 | 521 |
522 | 523 | `, 524 | styles: [` 525 | 526 | h1{ 527 | min-height: 200px; 528 | background-color: #ccc; 529 | text-align: center; 530 | line-height: 200px; 531 | } 532 | .leftRs { 533 | position: absolute; 534 | margin: auto; 535 | top: 0; 536 | bottom: 0; 537 | width: 50px; 538 | height: 50px; 539 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, .3); 540 | border-radius: 999px; 541 | left: 0; 542 | } 543 | 544 | .rightRs { 545 | position: absolute; 546 | margin: auto; 547 | top: 0; 548 | bottom: 0; 549 | width: 50px; 550 | height: 50px; 551 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, .3); 552 | border-radius: 999px; 553 | right: 0; 554 | } 555 | `] 556 | }) 557 | export class Sample implements OnInit { 558 | private carouselToken: string; 559 | 560 | public carouselTileItems: Array; 561 | public carouselTile: NguCarouselConfig; 562 | @ViewChild('carousel') carousel: NguCarousel; 563 | 564 | constructor() { } 565 | 566 | ngOnInit(){ 567 | this.carouselTileItems = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; 568 | 569 | this.carouselTile = { 570 | grid: {xs: 2, sm: 3, md: 3, lg: 5, xl:5, all: 0}, 571 | slide: 2, 572 | speed: 400, 573 | animation: 'lazy', 574 | point: { 575 | visible: true 576 | }, 577 | load: 2, 578 | touch: true, 579 | easing: 'ease' 580 | } 581 | } 582 | 583 | initDataFn(key: NguCarouselStore) { 584 | this.carouselToken = key.token; 585 | } 586 | 587 | resetFn() { 588 | this.carousel.reset(this.carouselToken); 589 | } 590 | 591 | moveToSlide() { 592 | this.carousel.moveToSlide(this.carouselToken, 2, false); 593 | } 594 | 595 | public carouselTileLoad(evt: any) { 596 | 597 | const len = this.carouselTileItems.length 598 | if (len <= 30) { 599 | for (let i = len; i < len + 10; i++) { 600 | this.carouselTileItems.push(i); 601 | } 602 | } 603 | 604 | } 605 | 606 | // carouselLoad will trigger this function when your load value reaches 607 | // it helps to load the data by parts to increase the performance of the app 608 | // must use feature to all carousel 609 | 610 | } 611 | ``` 612 | 613 | ## Tile with custom point style 614 | 615 | ```javascript 616 | import { Component } from '@angular/core'; 617 | import { NguCarousel } from '@ngu/carousel'; 618 | 619 | @Component({ 620 | selector: 'app-carousel', 621 | template: ` 622 | 625 | 626 | 627 |

{{Tile + 1}}

628 |
629 | 630 | 631 | 632 |
633 | `, 634 | styles: [` 635 | 636 | h1{ 637 | min-height: 200px; 638 | background-color: #ccc; 639 | text-align: center; 640 | line-height: 200px; 641 | } 642 | .leftRs { 643 | position: absolute; 644 | margin: auto; 645 | top: 0; 646 | bottom: 0; 647 | width: 50px; 648 | height: 50px; 649 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, .3); 650 | border-radius: 999px; 651 | left: 0; 652 | } 653 | 654 | .rightRs { 655 | position: absolute; 656 | margin: auto; 657 | top: 0; 658 | bottom: 0; 659 | width: 50px; 660 | height: 50px; 661 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, .3); 662 | border-radius: 999px; 663 | right: 0; 664 | } 665 | `] 666 | }) 667 | export class Sample implements OnInit { 668 | 669 | public carouselTileItems: Array; 670 | public carouselTile: NguCarousel; 671 | 672 | ngOnInit(){ 673 | this.carouselTileItems = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; 674 | 675 | this.carouselTile = { 676 | grid: {xs: 2, sm: 3, md: 3, lg: 5, xl:5, all: 0}, 677 | slide: 2, 678 | speed: 400, 679 | animation: 'lazy', 680 | point: { 681 | visible: true, 682 | pointStyles: ` 683 | .ngucarouselPoint { 684 | list-style-type: none; 685 | text-align: center; 686 | padding: 12px; 687 | margin: 0; 688 | white-space: nowrap; 689 | overflow: auto; 690 | box-sizing: border-box; 691 | } 692 | .ngucarouselPoint li { 693 | display: inline-block; 694 | border-radius: 50%; 695 | border: 2px solid rgba(0, 0, 0, 0.55); 696 | padding: 4px; 697 | margin: 0 3px; 698 | transition-timing-function: cubic-bezier(.17, .67, .83, .67); 699 | transition: .4s; 700 | } 701 | .ngucarouselPoint li.active { 702 | background: #6b6b6b; 703 | transform: scale(1.2); 704 | } 705 | ` 706 | }, 707 | load: 2, 708 | touch: true, 709 | easing: 'ease' 710 | } 711 | } 712 | 713 | public carouselTileLoad(evt: any) { 714 | 715 | const len = this.carouselTileItems.length 716 | if (len <= 30) { 717 | for (let i = len; i < len + 10; i++) { 718 | this.carouselTileItems.push(i); 719 | } 720 | } 721 | 722 | } 723 | 724 | // carouselLoad will trigger this function when your load value reaches 725 | // it helps to load the data by parts to increase the performance of the app 726 | // must use feature to all carousel 727 | 728 | } 729 | ``` 730 | 731 | ## License 732 | 733 | [MIT](LICENSE) license. 734 | 735 | ## Contributors ✨ 736 | 737 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 |
Code Veins
Code Veins

💻
Marcin Czachurski
Marcin Czachurski

💻
Alexander Buiko
Alexander Buiko

💻
Frido Koch
Frido Koch

💻
Giorgio Modoni
Giorgio Modoni

💻
faran312
faran312

💻
Euan Goddard
Euan Goddard

💻
Santosh Yadav
Santosh Yadav

💻
Tiago Magalhães
Tiago Magalhães

📖
Sam Vloeberghs
Sam Vloeberghs

📖
rat-matheson
rat-matheson

💻
Brandon Largeau
Brandon Largeau

💻
Artur Androsovych
Artur Androsovych

💻
chivesrs
chivesrs

💻
Luca Peruzzo
Luca Peruzzo

💻
Mateusz Stefańczyk
Mateusz Stefańczyk

💻
Matthieu Riegler
Matthieu Riegler

👀
Sasidharan SD
Sasidharan SD

📖
Jonatan Plata
Jonatan Plata

💻
771 | 772 | 773 | 774 | 775 | 776 | 777 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 778 | -------------------------------------------------------------------------------- /apps/ngu-carousel-e2e/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["src/plugins/index.js"], 11 | "rules": { 12 | "@typescript-eslint/no-var-requires": "off", 13 | "no-undef": "off" 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /apps/ngu-carousel-e2e/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress'; 2 | import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset'; 3 | 4 | export default defineConfig({ 5 | e2e: { 6 | ...nxE2EPreset(__dirname, { 7 | devServerTargets: { 8 | default: 'ngu-carousel-example:serve:development', 9 | production: 'ngu-carousel-example:serve:production' 10 | } 11 | }), 12 | /** 13 | * TODO(@nx/cypress): In Cypress v12,the testIsolation option is turned on by default. 14 | * This can cause tests to start breaking where not indended. 15 | * You should consider enabling this once you verify tests do not depend on each other 16 | * More Info: https://docs.cypress.io/guides/references/migration-guide#Test-Isolation 17 | **/ 18 | testIsolation: false 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /apps/ngu-carousel-e2e/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngu-carousel-e2e", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/ngu-carousel-e2e/src", 5 | "projectType": "application", 6 | "tags": [], 7 | "implicitDependencies": ["ngu-carousel-example"], 8 | "targets": { 9 | "lint": { 10 | "executor": "@nx/eslint:lint", 11 | "options": { 12 | "lintFilePatterns": ["{projectRoot}/**/*.{js,ts}"] 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/ngu-carousel-e2e/src/e2e/app.cy.ts: -------------------------------------------------------------------------------- 1 | import { getGreeting } from '../support/app.po'; 2 | 3 | describe('ngu-carousel-example', () => { 4 | beforeEach(() => cy.visit('/')); 5 | 6 | it('should display welcome message', () => { 7 | // Custom command example, see `../support/commands.ts` file 8 | cy.login('my-email@something.com', 'myPassword'); 9 | 10 | // Function helper example, see `../support/app.po.ts` file 11 | getGreeting().contains('Welcome ngu-carousel-example'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/ngu-carousel-e2e/src/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io" 4 | } 5 | -------------------------------------------------------------------------------- /apps/ngu-carousel-e2e/src/support/app.po.ts: -------------------------------------------------------------------------------- 1 | export const getGreeting = () => cy.get('h1'); 2 | -------------------------------------------------------------------------------- /apps/ngu-carousel-e2e/src/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | 11 | declare namespace Cypress { 12 | interface Chainable { 13 | login(email: string, password: string): void; 14 | } 15 | } 16 | // 17 | // -- This is a parent command -- 18 | Cypress.Commands.add('login', (email, password) => { 19 | console.log('Custom command example: Login', email, password); 20 | }); 21 | // 22 | // -- This is a child command -- 23 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 24 | // 25 | // 26 | // -- This is a dual command -- 27 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 28 | // 29 | // 30 | // -- This will overwrite an existing command -- 31 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 32 | -------------------------------------------------------------------------------- /apps/ngu-carousel-e2e/src/support/e2e.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | -------------------------------------------------------------------------------- /apps/ngu-carousel-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../../dist/out-tsc", 6 | "allowJs": true, 7 | "types": ["cypress", "node"] 8 | }, 9 | "include": ["src/**/*.ts", "src/**/*.js", "cypress.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 2020 4 | }, 5 | "ignorePatterns": ["!**/*"], 6 | "overrides": [ 7 | { 8 | "files": ["*.ts"], 9 | "parserOptions": { 10 | "project": ["apps/ngu-carousel-example/tsconfig.*?.json"], 11 | "createDefaultProgram": true 12 | }, 13 | "extends": [ 14 | "plugin:@angular-eslint/recommended", 15 | "plugin:@angular-eslint/template/process-inline-templates" 16 | ], 17 | "rules": { 18 | "@angular-eslint/component-selector": [ 19 | "error", 20 | { 21 | "prefix": "app", 22 | "style": "kebab-case", 23 | "type": "element" 24 | } 25 | ], 26 | "@angular-eslint/directive-selector": [ 27 | "error", 28 | { 29 | "prefix": "app", 30 | "style": "camelCase", 31 | "type": "attribute" 32 | } 33 | ] 34 | } 35 | }, 36 | { 37 | "files": ["*.html"], 38 | "extends": ["plugin:@angular-eslint/template/recommended"], 39 | "rules": {} 40 | } 41 | ], 42 | "extends": "../../.eslintrc.json" 43 | } 44 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | const path = require('path'); 5 | 6 | module.exports = function (config) { 7 | config.set({ 8 | basePath: '', 9 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 10 | plugins: [ 11 | require('karma-jasmine'), 12 | require('karma-chrome-launcher'), 13 | require('karma-coverage'), 14 | require('karma-jasmine-html-reporter'), 15 | require('@angular-devkit/build-angular/plugins/karma') 16 | ], 17 | client: { 18 | clearContext: false // leave Jasmine Spec Runner output visible in browser 19 | }, 20 | coverageReporter: { 21 | dir: path.join(__dirname, 'reports', 'coverage'), 22 | subdir: '.', 23 | reporters: [{ type: 'lcov' }] 24 | }, 25 | reporters: ['progress', 'kjhtml', 'coverage'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome'], 31 | customLaunchers: { 32 | ChromeHeadlessCI: { 33 | base: 'ChromeHeadless', 34 | flags: ['--no-sandbox', '--disable-gpu'] 35 | } 36 | }, 37 | singleRun: false, 38 | restartOnFileChange: true 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngu-carousel-example", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "application", 5 | "sourceRoot": "apps/ngu-carousel-example/src", 6 | "prefix": "app", 7 | "generators": { 8 | "@schematics/angular:component": { 9 | "style": "scss" 10 | }, 11 | "@schematics/angular:application": { 12 | "strict": true 13 | } 14 | }, 15 | "targets": { 16 | "build": { 17 | "executor": "@angular-devkit/build-angular:application", 18 | "options": { 19 | "outputPath": "dist/apps/ngu-carousel-example", 20 | "index": "apps/ngu-carousel-example/src/index.html", 21 | "browser": "apps/ngu-carousel-example/src/main.ts", 22 | "polyfills": [], 23 | "tsConfig": "apps/ngu-carousel-example/tsconfig.app.json", 24 | "inlineStyleLanguage": "scss", 25 | "assets": [ 26 | "apps/ngu-carousel-example/src/favicon.ico", 27 | "apps/ngu-carousel-example/src/assets", 28 | "apps/ngu-carousel-example/src/src/_redirects" 29 | ], 30 | "styles": [ 31 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", 32 | "apps/ngu-carousel-example/src/styles.scss" 33 | ], 34 | "scripts": [], 35 | "allowedCommonJsDependencies": [ 36 | "hammerjs" 37 | ] 38 | }, 39 | "configurations": { 40 | "production": { 41 | "budgets": [ 42 | { 43 | "type": "initial", 44 | "maximumWarning": "2mb", 45 | "maximumError": "5mb" 46 | }, 47 | { 48 | "type": "anyComponentStyle", 49 | "maximumWarning": "6kb", 50 | "maximumError": "10kb" 51 | } 52 | ], 53 | "outputHashing": "all" 54 | }, 55 | "development": { 56 | "optimization": false, 57 | "extractLicenses": false, 58 | "sourceMap": true, 59 | "namedChunks": true 60 | } 61 | }, 62 | "defaultConfiguration": "production" 63 | }, 64 | "serve": { 65 | "executor": "@angular-devkit/build-angular:dev-server", 66 | "configurations": { 67 | "production": { 68 | "buildTarget": "ngu-carousel-example:build:production" 69 | }, 70 | "development": { 71 | "buildTarget": "ngu-carousel-example:build:development" 72 | } 73 | }, 74 | "defaultConfiguration": "development" 75 | }, 76 | "extract-i18n": { 77 | "executor": "@angular-devkit/build-angular:extract-i18n", 78 | "options": { 79 | "buildTarget": "ngu-carousel-example:build" 80 | } 81 | }, 82 | "test": { 83 | "executor": "@angular-devkit/build-angular:karma", 84 | "options": { 85 | "browser": "apps/ngu-carousel-example/src/test.ts", 86 | "polyfills": [], 87 | "tsConfig": "apps/ngu-carousel-example/tsconfig.spec.json", 88 | "karmaConfig": "apps/ngu-carousel-example/karma.conf.js", 89 | "inlineStyleLanguage": "scss", 90 | "assets": [ 91 | "apps/ngu-carousel-example/src/favicon.ico", 92 | "apps/ngu-carousel-example/src/assets" 93 | ], 94 | "styles": [ 95 | "apps/ngu-carousel-example/src/styles.scss" 96 | ], 97 | "scripts": [] 98 | } 99 | }, 100 | "lint": { 101 | "executor": "@nx/eslint:lint", 102 | "options": { 103 | "lintFilePatterns": [ 104 | "{projectRoot}/**/*.ts", 105 | "{projectRoot}/**/*.html" 106 | ] 107 | } 108 | }, 109 | "generate-bundle": { 110 | "executor": "@ngx-builders/analyze:analyze", 111 | "options": { 112 | "outputPath": "dist/apps/ngu-carousel-example", 113 | "reportPath": "reports" 114 | } 115 | }, 116 | "server": { 117 | "executor": "@angular-devkit/build-angular:server", 118 | "options": { 119 | "outputPath": "dist/apps/ngu-carousel-example/server", 120 | "main": "apps/ngu-carousel-example/server.ts", 121 | "tsConfig": "apps/ngu-carousel-example/tsconfig.server.json", 122 | "inlineStyleLanguage": "scss" 123 | }, 124 | "configurations": { 125 | "production": { 126 | "outputHashing": "media" 127 | }, 128 | "development": { 129 | "buildOptimizer": false, 130 | "optimization": false, 131 | "sourceMap": true, 132 | "extractLicenses": false, 133 | "vendorChunk": true 134 | } 135 | }, 136 | "defaultConfiguration": "production" 137 | }, 138 | "serve-ssr": { 139 | "executor": "@angular-devkit/build-angular:ssr-dev-server", 140 | "configurations": { 141 | "development": { 142 | "browserTarget": "ngu-carousel-example:build:development", 143 | "serverTarget": "ngu-carousel-example:server:development" 144 | }, 145 | "production": { 146 | "browserTarget": "ngu-carousel-example:build:production", 147 | "serverTarget": "ngu-carousel-example:server:production" 148 | } 149 | }, 150 | "defaultConfiguration": "development" 151 | }, 152 | "prerender": { 153 | "executor": "@angular-devkit/build-angular:prerender", 154 | "options": { 155 | "routes": [ 156 | "/" 157 | ] 158 | }, 159 | "configurations": { 160 | "production": { 161 | "browserTarget": "ngu-carousel-example:build:production", 162 | "serverTarget": "ngu-carousel-example:server:production" 163 | }, 164 | "development": { 165 | "browserTarget": "ngu-carousel-example:build:development", 166 | "serverTarget": "ngu-carousel-example:server:development" 167 | } 168 | }, 169 | "defaultConfiguration": "production" 170 | } 171 | } 172 | } -------------------------------------------------------------------------------- /apps/ngu-carousel-example/server.ts: -------------------------------------------------------------------------------- 1 | import { APP_BASE_HREF } from '@angular/common'; 2 | import { CommonEngine } from '@angular/ssr/node'; 3 | import express from 'express'; 4 | import { fileURLToPath } from 'node:url'; 5 | import { dirname, join, resolve } from 'node:path'; 6 | import bootstrap from './src/main.server'; 7 | 8 | // The Express app is exported so that it can be used by serverless Functions. 9 | export function app(): express.Express { 10 | const server = express(); 11 | const serverDistFolder = dirname(fileURLToPath(import.meta.url)); 12 | const browserDistFolder = resolve(serverDistFolder, '../browser'); 13 | const indexHtml = join(serverDistFolder, 'index.server.html'); 14 | 15 | const commonEngine = new CommonEngine(); 16 | 17 | server.set('view engine', 'html'); 18 | server.set('views', browserDistFolder); 19 | 20 | // Example Express Rest API endpoints 21 | // server.get('/api/**', (req, res) => { }); 22 | // Serve static files from /browser 23 | server.get( 24 | '**', 25 | express.static(browserDistFolder, { 26 | maxAge: '1y', 27 | index: 'index.html' 28 | }) 29 | ); 30 | 31 | // All regular routes use the Angular engine 32 | server.get('**', (req, res, next) => { 33 | const { protocol, originalUrl, baseUrl, headers } = req; 34 | 35 | commonEngine 36 | .render({ 37 | bootstrap, 38 | documentFilePath: indexHtml, 39 | url: `${protocol}://${headers.host}${originalUrl}`, 40 | publicPath: browserDistFolder, 41 | providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }] 42 | }) 43 | .then(html => res.send(html)) 44 | .catch(err => next(err)); 45 | }); 46 | 47 | return server; 48 | } 49 | 50 | function run(): void { 51 | const port = process.env['PORT'] || 4000; 52 | 53 | // Start up the Node server 54 | const server = app(); 55 | server.listen(port, () => { 56 | console.log(`Node Express server listening on http://localhost:${port}`); 57 | }); 58 | } 59 | 60 | run(); 61 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/app-server.config.ts: -------------------------------------------------------------------------------- 1 | import { mergeApplicationConfig, ApplicationConfig } from '@angular/core'; 2 | import { provideServerRendering } from '@angular/platform-server'; 3 | import { appConfig } from './app.config'; 4 | 5 | const serverConfig: ApplicationConfig = { 6 | providers: [provideServerRendering()] 7 | }; 8 | 9 | export const config = mergeApplicationConfig(appConfig, serverConfig); 10 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiuniversal/ngu-carousel/735c71291bd3af93980adb6aafc6b701abb808d3/apps/ngu-carousel-example/src/app/app.component.scss -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { LayoutModule } from '@angular/cdk/layout'; 2 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 3 | import { MatButtonModule } from '@angular/material/button'; 4 | import { MatIconModule } from '@angular/material/icon'; 5 | import { MatListModule } from '@angular/material/list'; 6 | import { MatSidenavModule } from '@angular/material/sidenav'; 7 | import { MatToolbarModule } from '@angular/material/toolbar'; 8 | import { NoopAnimationsModule } from '@angular/platform-browser/animations'; 9 | import { RouterTestingModule } from '@angular/router/testing'; 10 | import { AppComponent } from './app.component'; 11 | import { MainNavComponent } from './main-nav/main-nav.component'; 12 | 13 | describe('AppComponent', () => { 14 | let component: AppComponent; 15 | let fixture: ComponentFixture; 16 | 17 | beforeEach(waitForAsync(() => { 18 | TestBed.configureTestingModule({ 19 | imports: [ 20 | NoopAnimationsModule, 21 | LayoutModule, 22 | MatButtonModule, 23 | MatIconModule, 24 | MatListModule, 25 | MatSidenavModule, 26 | MatToolbarModule, 27 | RouterTestingModule, 28 | MainNavComponent, 29 | AppComponent 30 | ] 31 | }).compileComponents(); 32 | })); 33 | 34 | beforeEach(() => { 35 | fixture = TestBed.createComponent(AppComponent); 36 | component = fixture.componentInstance; 37 | fixture.detectChanges(); 38 | }); 39 | 40 | it('should create the app', () => { 41 | expect(component).toBeTruthy(); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { MainNavComponent } from './main-nav/main-nav.component'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.scss'], 8 | changeDetection: ChangeDetectionStrategy.OnPush, 9 | imports: [MainNavComponent] 10 | }) 11 | export class AppComponent {} 12 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/app.config.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationConfig, provideExperimentalZonelessChangeDetection } from '@angular/core'; 2 | import { provideAnimations } from '@angular/platform-browser/animations'; 3 | import { provideRouter } from '@angular/router'; 4 | import { APP_ROUTES } from './app.routes'; 5 | 6 | export const appConfig: ApplicationConfig = { 7 | providers: [ 8 | provideRouter(APP_ROUTES), 9 | provideAnimations(), 10 | provideExperimentalZonelessChangeDetection() 11 | ] 12 | }; 13 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { GettingStartedComponent } from './getting-started/getting-started.component'; 3 | 4 | export const APP_ROUTES: Routes = [ 5 | { 6 | path: 'banner', 7 | loadComponent: () => import('./banner/banner.component').then(m => m.BannerComponent) 8 | }, 9 | { 10 | path: 'tile', 11 | loadComponent: () => import('./tile/tile.component').then(m => m.TileComponent) 12 | }, 13 | { 14 | path: 'tile-2-images', 15 | loadComponent: () => 16 | import('./tile-2-images/tile-2-images.component').then(m => m.Tile2ImagesComponent) 17 | }, 18 | { 19 | path: 'banner-vertical', 20 | loadComponent: () => 21 | import('./banner-vertical/banner-vertical.component').then(m => m.BannerVerticalComponent) 22 | }, 23 | { 24 | path: 'wrapped', 25 | loadComponent: () => import('./wrapped/wrapped.component').then(m => m.WrappedComponent) 26 | }, 27 | { 28 | path: 'getting-started', 29 | component: GettingStartedComponent 30 | }, 31 | { 32 | path: '', 33 | redirectTo: 'tile', 34 | pathMatch: 'full' 35 | } 36 | ]; 37 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/banner-vertical/banner-vertical.component.html: -------------------------------------------------------------------------------- 1 |

Banner Vertical

2 | 3 | 4 | 5 | 6 |
7 |

{{ i + 1 }}

8 |
9 |
10 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/banner-vertical/banner-vertical.component.scss: -------------------------------------------------------------------------------- 1 | .bannerStyle h1 { 2 | min-height: 300px; 3 | text-align: center; 4 | line-height: 300px; 5 | } 6 | .leftRs { 7 | position: absolute; 8 | margin: auto; 9 | top: 0; 10 | bottom: 0; 11 | width: 50px; 12 | height: 50px; 13 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 14 | border-radius: 999px; 15 | left: 0; 16 | z-index: 1; 17 | } 18 | 19 | .rightRs { 20 | position: absolute; 21 | margin: auto; 22 | top: 0; 23 | bottom: 0; 24 | width: 50px; 25 | height: 50px; 26 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 27 | border-radius: 999px; 28 | right: 0; 29 | z-index: 1; 30 | } 31 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/banner-vertical/banner-vertical.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { NguCarousel } from '@ngu/carousel'; 3 | 4 | import { BannerVerticalComponent } from './banner-vertical.component'; 5 | 6 | describe('BannerVerticalComponent', () => { 7 | let component: BannerVerticalComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | imports: [NguCarousel, BannerVerticalComponent] 13 | }).compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(BannerVerticalComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/banner-vertical/banner-vertical.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, afterNextRender, signal } from '@angular/core'; 2 | import { NguCarouselConfig } from '@ngu/carousel'; 3 | import { slider } from '../slide-animation'; 4 | import { 5 | NguItemComponent, 6 | NguCarouselPrevDirective, 7 | NguCarouselDefDirective, 8 | NguCarouselNextDirective, 9 | NguCarousel 10 | } from '@ngu/carousel'; 11 | 12 | @Component({ 13 | selector: 'app-banner-vertical', 14 | templateUrl: './banner-vertical.component.html', 15 | styleUrls: ['./banner-vertical.component.scss'], 16 | changeDetection: ChangeDetectionStrategy.OnPush, 17 | animations: [slider], 18 | imports: [ 19 | NguCarousel, 20 | NguCarouselPrevDirective, 21 | NguCarouselDefDirective, 22 | NguItemComponent, 23 | NguCarouselNextDirective 24 | ] 25 | }) 26 | export class BannerVerticalComponent { 27 | images = ['assets/bg.jpg', 'assets/car.png', 'assets/canberra.jpg', 'assets/holi.jpg']; 28 | carouselBanner: NguCarouselConfig = { 29 | grid: { xs: 1, sm: 1, md: 1, lg: 1, all: 0 }, 30 | slide: 1, 31 | speed: 400, 32 | interval: { 33 | timing: 3000, 34 | initialDelay: 1000 35 | }, 36 | point: { 37 | visible: true 38 | }, 39 | load: 2, 40 | custom: 'banner', 41 | loop: true, 42 | touch: true, // touch is not currently in active for vertical carousel, will enable it in future build 43 | vertical: { 44 | enabled: true, 45 | height: 400 46 | } 47 | }; 48 | tempData: any[]; 49 | 50 | public items = signal([]); 51 | 52 | constructor() { 53 | this.addItem(); 54 | afterNextRender(() => { 55 | const id = setInterval(() => { 56 | this.addItem(); 57 | if (this.items().length >= 30) { 58 | clearInterval(id); 59 | } 60 | }, 500); 61 | }); 62 | } 63 | 64 | addItem() { 65 | const i = Math.floor(Math.random() * this.images.length); 66 | this.items.update(items => [...items, this.images[i]]); 67 | } 68 | 69 | /* It will be triggered on every slide*/ 70 | onmoveFn(data: any) { 71 | console.log(data); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/banner/banner.component.html: -------------------------------------------------------------------------------- 1 |

Banner

2 | 8 | 9 | 10 | 11 |
12 |

{{ i + 1 }}

13 |
14 |
15 | 16 | 17 |
18 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/banner/banner.component.scss: -------------------------------------------------------------------------------- 1 | .bannerStyle h1 { 2 | min-height: 300px; 3 | text-align: center; 4 | line-height: 300px; 5 | } 6 | .leftRs { 7 | position: absolute; 8 | margin: auto; 9 | top: 0; 10 | bottom: 0; 11 | width: 50px; 12 | height: 50px; 13 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 14 | border-radius: 999px; 15 | left: 0; 16 | z-index: 1; 17 | } 18 | 19 | .rightRs { 20 | position: absolute; 21 | margin: auto; 22 | top: 0; 23 | bottom: 0; 24 | width: 50px; 25 | height: 50px; 26 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 27 | border-radius: 999px; 28 | right: 0; 29 | z-index: 1; 30 | } 31 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/banner/banner.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { NguCarousel } from '@ngu/carousel'; 3 | 4 | import { BannerComponent } from './banner.component'; 5 | 6 | describe('BannerComponent', () => { 7 | let component: BannerComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | imports: [NguCarousel, BannerComponent] 13 | }).compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(BannerComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/banner/banner.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, afterNextRender, signal } from '@angular/core'; 2 | import { NguCarouselConfig } from '@ngu/carousel'; 3 | import { slider } from '../slide-animation'; 4 | import { 5 | NguItemComponent, 6 | NguCarouselPrevDirective, 7 | NguCarouselDefDirective, 8 | NguCarouselNextDirective, 9 | NguCarousel 10 | } from '@ngu/carousel'; 11 | 12 | @Component({ 13 | selector: 'app-banner', 14 | templateUrl: './banner.component.html', 15 | styleUrls: ['./banner.component.scss'], 16 | animations: [slider], 17 | changeDetection: ChangeDetectionStrategy.OnPush, 18 | imports: [ 19 | NguCarousel, 20 | NguCarouselPrevDirective, 21 | NguCarouselDefDirective, 22 | NguItemComponent, 23 | NguCarouselNextDirective 24 | ] 25 | }) 26 | export class BannerComponent { 27 | images = ['assets/bg.jpg', 'assets/car.png', 'assets/canberra.jpg', 'assets/holi.jpg']; 28 | carouselBanner: NguCarouselConfig = { 29 | grid: { xs: 1, sm: 1, md: 1, lg: 1, all: 0 }, 30 | slide: 1, 31 | speed: 400, 32 | interval: { 33 | timing: 3000, 34 | initialDelay: 1000 35 | }, 36 | point: { 37 | visible: true 38 | }, 39 | load: 2, 40 | custom: 'banner', 41 | loop: true, 42 | touch: true, // touch is not currently in active for vertical carousel, will enable it in future build 43 | vertical: { 44 | enabled: false, 45 | height: 400 46 | } 47 | }; 48 | tempData: any[]; 49 | 50 | public items = signal([]); 51 | 52 | constructor() { 53 | this.addItem(); 54 | afterNextRender(() => { 55 | const id = setInterval(() => { 56 | this.addItem(); 57 | if (this.items().length >= 30) { 58 | clearInterval(id); 59 | } 60 | }, 500); 61 | }); 62 | } 63 | 64 | addItem() { 65 | const i = Math.floor(Math.random() * this.images.length); 66 | this.items.update(items => [...items, this.images[i]]); 67 | } 68 | 69 | /* It will be triggered on every slide*/ 70 | onmoveFn(data: any) { 71 | console.log(data); 72 | } 73 | 74 | trackCarousel(_, item) { 75 | return item; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/getting-started/getting-started.component.html: -------------------------------------------------------------------------------- 1 |

getting-started works!

2 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/getting-started/getting-started.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiuniversal/ngu-carousel/735c71291bd3af93980adb6aafc6b701abb808d3/apps/ngu-carousel-example/src/app/getting-started/getting-started.component.scss -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/getting-started/getting-started.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { NguCarousel } from '@ngu/carousel'; 3 | 4 | import { GettingStartedComponent } from './getting-started.component'; 5 | 6 | describe('GettingStartedComponent', () => { 7 | let component: GettingStartedComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | imports: [NguCarousel, GettingStartedComponent] 13 | }).compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(GettingStartedComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/getting-started/getting-started.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-getting-started', 5 | templateUrl: './getting-started.component.html', 6 | styleUrls: ['./getting-started.component.scss'], 7 | changeDetection: ChangeDetectionStrategy.OnPush 8 | }) 9 | export class GettingStartedComponent {} 10 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/main-nav/main-nav.component.html: -------------------------------------------------------------------------------- 1 | 2 | 10 | Banner Types 11 | 12 | Tile 13 | Banner 14 | Banner Vertical 15 | Tile with 2 Images 16 | Wrapped Carousel 17 | 18 | 19 | 20 | 21 | @if (isHandset()) { 22 | 25 | } 26 | ngu-carousel Documentation 27 | 28 | 34 | Github 39 | GitHub 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/main-nav/main-nav.component.scss: -------------------------------------------------------------------------------- 1 | .sidenav-container { 2 | height: 100%; 3 | } 4 | 5 | .sidenav { 6 | width: 200px; 7 | } 8 | 9 | .sidenav .mat-toolbar { 10 | background: inherit; 11 | } 12 | 13 | .mat-toolbar.mat-primary { 14 | position: sticky; 15 | top: 0; 16 | z-index: 1; 17 | } 18 | 19 | .example-spacer { 20 | flex: 1 1 auto; 21 | } 22 | .github-button, 23 | .github-button:hover { 24 | color: #fff; 25 | text-decoration: none; 26 | } 27 | .github-icon { 28 | height: 25px; 29 | margin: 0 4px 3px 0; 30 | vertical-align: middle; 31 | } 32 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/main-nav/main-nav.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { LayoutModule } from '@angular/cdk/layout'; 2 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 3 | import { RouterTestingModule } from '@angular/router/testing'; 4 | import { MatButtonModule } from '@angular/material/button'; 5 | import { MatIconModule } from '@angular/material/icon'; 6 | import { MatListModule } from '@angular/material/list'; 7 | import { MatSidenavContainer, MatSidenavModule } from '@angular/material/sidenav'; 8 | import { MatToolbarModule } from '@angular/material/toolbar'; 9 | import { NoopAnimationsModule } from '@angular/platform-browser/animations'; 10 | 11 | import { MainNavComponent } from './main-nav.component'; 12 | 13 | describe('MainNavComponent', () => { 14 | let component: MainNavComponent; 15 | let fixture: ComponentFixture; 16 | 17 | beforeEach(waitForAsync(() => { 18 | TestBed.configureTestingModule({ 19 | imports: [ 20 | NoopAnimationsModule, 21 | LayoutModule, 22 | MatButtonModule, 23 | MatIconModule, 24 | MatListModule, 25 | MatSidenavModule, 26 | MatToolbarModule, 27 | RouterTestingModule, 28 | MainNavComponent 29 | ] 30 | }).compileComponents(); 31 | })); 32 | 33 | beforeEach(() => { 34 | fixture = TestBed.createComponent(MainNavComponent); 35 | component = fixture.componentInstance; 36 | fixture.detectChanges(); 37 | }); 38 | 39 | it('should compile', () => { 40 | expect(component).toBeTruthy(); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/main-nav/main-nav.component.ts: -------------------------------------------------------------------------------- 1 | import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout'; 2 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 3 | import { toSignal } from '@angular/core/rxjs-interop'; 4 | import { MatIcon } from '@angular/material/icon'; 5 | import { MatIconButton, MatAnchor } from '@angular/material/button'; 6 | import { RouterLink, RouterOutlet } from '@angular/router'; 7 | import { MatNavList, MatListItem } from '@angular/material/list'; 8 | import { MatToolbar } from '@angular/material/toolbar'; 9 | import { MatSidenavContainer, MatSidenav, MatSidenavContent } from '@angular/material/sidenav'; 10 | import { map } from 'rxjs'; 11 | 12 | @Component({ 13 | selector: 'app-main-nav', 14 | templateUrl: './main-nav.component.html', 15 | styleUrls: ['./main-nav.component.scss'], 16 | changeDetection: ChangeDetectionStrategy.OnPush, 17 | imports: [ 18 | MatSidenavContainer, 19 | MatSidenav, 20 | MatToolbar, 21 | MatNavList, 22 | MatListItem, 23 | RouterLink, 24 | MatSidenavContent, 25 | MatIconButton, 26 | MatIcon, 27 | MatAnchor, 28 | RouterOutlet 29 | ] 30 | }) 31 | export class MainNavComponent { 32 | isHandset = toSignal( 33 | this.breakpointObserver.observe(Breakpoints.Handset).pipe(map(result => result.matches)) 34 | ); 35 | 36 | constructor(private breakpointObserver: BreakpointObserver) {} 37 | } 38 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/slide-animation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | animate, 3 | AnimationTriggerMetadata, 4 | state, 5 | style, 6 | transition, 7 | trigger 8 | } from '@angular/animations'; 9 | 10 | /** Time and timing curve for expansion panel animations. */ 11 | export const EXPANSION_PANEL_ANIMATION_TIMING = '225ms cubic-bezier(0.4,0.0,0.2,1)'; 12 | 13 | /** Animations used by the Material expansion panel. */ 14 | export const slider: AnimationTriggerMetadata = trigger('slider', [ 15 | state('true', style({ transform: 'translate3d({{distance}}px,0,0)' }), { 16 | params: { distance: '0' } 17 | }), 18 | state('false', style({ transform: 'translate3d({{distance}}px,0,0)' }), { 19 | params: { distance: '0' } 20 | }), 21 | transition('* => *', animate('200ms ease-in')) 22 | ]); 23 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/tile-2-images/tile-2-images.component.css: -------------------------------------------------------------------------------- 1 | .bannerStyle div { 2 | background-color: #ccc; 3 | background-size: cover !important; 4 | height: 100%; 5 | } 6 | 7 | .bannerStyle img { 8 | width: 100%; 9 | height: 100%; 10 | } 11 | 12 | .bannerStyle h1 { 13 | text-align: center; 14 | background: rgba(0, 0, 0, 0.32); 15 | color: white; 16 | margin: 0; 17 | height: 100%; 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | } 22 | 23 | .tile { 24 | min-height: 200px; 25 | background-color: #ccc; 26 | background-size: cover !important; 27 | } 28 | 29 | .tile h1 { 30 | text-align: center; 31 | line-height: 200px; 32 | background: rgba(0, 0, 0, 0.32); 33 | color: white; 34 | margin: 0; 35 | } 36 | 37 | h4 { 38 | margin: 0; 39 | padding: 10px 15px; 40 | text-align: center; 41 | } 42 | 43 | p { 44 | margin: 0; 45 | padding: 0 15px 10px; 46 | text-align: center; 47 | } 48 | 49 | .wBg { 50 | background: white; 51 | } 52 | 53 | .container { 54 | max-width: 1200px; 55 | margin: 0 auto; 56 | } 57 | 58 | nav { 59 | border-bottom: 1px solid #ccc; 60 | position: fixed; 61 | width: 100%; 62 | top: 0; 63 | background: white; 64 | z-index: 12; 65 | } 66 | 67 | nav h1 { 68 | margin: 0; 69 | padding: 10px; 70 | text-align: center; 71 | } 72 | 73 | .carBtn { 74 | position: absolute; 75 | margin: auto; 76 | top: 0; 77 | bottom: 0; 78 | width: 50px; 79 | height: 50px; 80 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 81 | border-radius: 999px; 82 | } 83 | 84 | .leftRs { 85 | position: absolute; 86 | margin: auto; 87 | top: 0; 88 | bottom: 0; 89 | width: 50px; 90 | height: 50px; 91 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 92 | border-radius: 999px; 93 | left: 0; 94 | z-index: 1; 95 | } 96 | 97 | .rightRs { 98 | position: absolute; 99 | margin: auto; 100 | top: 0; 101 | bottom: 0; 102 | width: 50px; 103 | height: 50px; 104 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 105 | border-radius: 999px; 106 | right: 0; 107 | z-index: 1; 108 | } 109 | 110 | .leftRs1 { 111 | position: absolute; 112 | margin: auto; 113 | top: 0; 114 | bottom: 0; 115 | width: 50px; 116 | height: 50px; 117 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 118 | border-radius: 999px; 119 | right: 0; 120 | } 121 | 122 | .rightRs1 { 123 | position: absolute; 124 | margin: auto; 125 | top: 0; 126 | bottom: 0; 127 | width: 50px; 128 | height: 50px; 129 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 130 | border-radius: 999px; 131 | left: 0; 132 | } 133 | 134 | .myPoint { 135 | list-style-type: none; 136 | text-align: center; 137 | padding: 12px; 138 | margin: 0; 139 | white-space: nowrap; 140 | overflow: auto; 141 | box-sizing: border-box; 142 | li { 143 | display: inline-block; 144 | border-radius: 50%; 145 | border: 2px solid rgba(0, 0, 0, 0.55); 146 | padding: 4px; 147 | margin: 0 3px; 148 | transition-timing-function: cubic-bezier(0.17, 0.67, 0.83, 0.67); 149 | transition: 0.4s; 150 | &.active { 151 | background: #6b6b6b; 152 | transform: scale(1.2); 153 | } 154 | } 155 | } 156 | 157 | .carouselHoved { 158 | opacity: 0.4; 159 | } 160 | 161 | .myBannerPoint { 162 | position: absolute; 163 | display: -webkit-box; 164 | display: -ms-flexbox; 165 | display: flex; 166 | width: 100%; 167 | left: 0; 168 | bottom: 0; 169 | padding: 0; 170 | color: beige; 171 | justify-content: center; 172 | list-style-type: none; 173 | background: rgba(0, 0, 0, 0.34); 174 | margin: 0; 175 | padding: 10px; 176 | box-sizing: border-box; 177 | li { 178 | background: #6b6b6b; 179 | width: 50px; 180 | height: 50px; 181 | margin-right: 10px; 182 | &.active { 183 | transform: translateY(-10px); 184 | transition: 0.3s ease all; 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/tile-2-images/tile-2-images.component.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 |
8 |

{{ i + 1 }}

9 |
10 |
11 | 12 | 15 | 16 |
    17 | @for (i of myCarousel.pointNumbers(); track i) { 18 |
  • 19 | } 20 |
21 |
22 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/tile-2-images/tile-2-images.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { NguCarousel } from '@ngu/carousel'; 3 | import { Tile2ImagesComponent } from './tile-2-images.component'; 4 | 5 | describe('Tile2ImagesComponent', () => { 6 | let component: Tile2ImagesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [NguCarousel, Tile2ImagesComponent] 12 | }).compileComponents(); 13 | 14 | fixture = TestBed.createComponent(Tile2ImagesComponent); 15 | component = fixture.componentInstance; 16 | fixture.detectChanges(); 17 | }); 18 | 19 | it('should create', () => { 20 | expect(component).toBeTruthy(); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/tile-2-images/tile-2-images.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, afterNextRender, signal } from '@angular/core'; 2 | import { NguCarouselConfig } from '@ngu/carousel'; 3 | import { slider } from '../slide-animation'; 4 | import { 5 | NguTileComponent, 6 | NguCarouselPrevDirective, 7 | NguCarouselDefDirective, 8 | NguCarouselNextDirective, 9 | NguCarousel 10 | } from '@ngu/carousel'; 11 | 12 | @Component({ 13 | selector: 'app-tile-2-images', 14 | templateUrl: './tile-2-images.component.html', 15 | styleUrls: ['./tile-2-images.component.css'], 16 | animations: [slider], 17 | changeDetection: ChangeDetectionStrategy.OnPush, 18 | imports: [ 19 | NguCarousel, 20 | NguCarouselPrevDirective, 21 | NguCarouselDefDirective, 22 | NguTileComponent, 23 | NguCarouselNextDirective 24 | ] 25 | }) 26 | export class Tile2ImagesComponent { 27 | images = ['assets/bg.jpg', 'assets/car.png']; 28 | 29 | public carouselItems = signal([]); 30 | public carouselTileConfig: NguCarouselConfig = { 31 | grid: { xs: 1, sm: 1, md: 1, lg: 5, all: 0 }, 32 | speed: 250, 33 | point: { 34 | visible: true 35 | }, 36 | touch: true, 37 | loop: true, 38 | interval: { timing: 1500 }, 39 | animation: 'lazy' 40 | }; 41 | 42 | constructor() { 43 | this.addItem(); 44 | afterNextRender(() => { 45 | const id = setInterval(() => { 46 | this.addItem(); 47 | if (this.carouselItems().length >= 2) { 48 | clearInterval(id); 49 | } 50 | }, 500); 51 | }); 52 | } 53 | 54 | addItem() { 55 | const i = Math.floor(Math.random() * this.images.length); 56 | this.carouselItems.update(items => [...items, this.images[i]]); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/tile/tile.component.html: -------------------------------------------------------------------------------- 1 |

Tile

2 | 3 | 4 | 7 | 8 | 9 |
10 |

{{ i + 1 }}

11 |
12 |
13 | 14 | 17 | 18 |
    19 | @for (i of myCarousel.pointNumbers(); track i) { 20 |
  • 21 | } 22 |
23 |
24 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/tile/tile.component.scss: -------------------------------------------------------------------------------- 1 | .bannerStyle div { 2 | background-color: #ccc; 3 | background-size: cover !important; 4 | height: 100%; 5 | } 6 | 7 | .bannerStyle img { 8 | width: 100%; 9 | height: 100%; 10 | } 11 | 12 | .bannerStyle h1 { 13 | text-align: center; 14 | background: rgba(0, 0, 0, 0.32); 15 | color: white; 16 | margin: 0; 17 | height: 100%; 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | } 22 | 23 | .tile { 24 | min-height: 200px; 25 | background-color: #ccc; 26 | background-size: cover !important; 27 | } 28 | 29 | .tile h1 { 30 | text-align: center; 31 | line-height: 200px; 32 | background: rgba(0, 0, 0, 0.32); 33 | color: white; 34 | margin: 0; 35 | } 36 | 37 | h4 { 38 | margin: 0; 39 | padding: 10px 15px; 40 | text-align: center; 41 | } 42 | 43 | p { 44 | margin: 0; 45 | padding: 0 15px 10px; 46 | text-align: center; 47 | } 48 | 49 | .wBg { 50 | background: white; 51 | } 52 | 53 | .container { 54 | max-width: 1200px; 55 | margin: 0 auto; 56 | } 57 | 58 | nav { 59 | border-bottom: 1px solid #ccc; 60 | position: fixed; 61 | width: 100%; 62 | top: 0; 63 | background: white; 64 | z-index: 12; 65 | } 66 | 67 | nav h1 { 68 | margin: 0; 69 | padding: 10px; 70 | text-align: center; 71 | } 72 | 73 | .carBtn { 74 | position: absolute; 75 | margin: auto; 76 | top: 0; 77 | bottom: 0; 78 | width: 50px; 79 | height: 50px; 80 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 81 | border-radius: 999px; 82 | } 83 | 84 | .leftRs { 85 | position: absolute; 86 | margin: auto; 87 | top: 0; 88 | bottom: 0; 89 | width: 50px; 90 | height: 50px; 91 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 92 | border-radius: 999px; 93 | left: 0; 94 | z-index: 1; 95 | } 96 | 97 | .rightRs { 98 | position: absolute; 99 | margin: auto; 100 | top: 0; 101 | bottom: 0; 102 | width: 50px; 103 | height: 50px; 104 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 105 | border-radius: 999px; 106 | right: 0; 107 | z-index: 1; 108 | } 109 | 110 | .leftRs1 { 111 | position: absolute; 112 | margin: auto; 113 | top: 0; 114 | bottom: 0; 115 | width: 50px; 116 | height: 50px; 117 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 118 | border-radius: 999px; 119 | right: 0; 120 | } 121 | 122 | .rightRs1 { 123 | position: absolute; 124 | margin: auto; 125 | top: 0; 126 | bottom: 0; 127 | width: 50px; 128 | height: 50px; 129 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 130 | border-radius: 999px; 131 | left: 0; 132 | } 133 | 134 | .myPoint { 135 | list-style-type: none; 136 | text-align: center; 137 | padding: 12px; 138 | margin: 0; 139 | white-space: nowrap; 140 | overflow: auto; 141 | box-sizing: border-box; 142 | li { 143 | display: inline-block; 144 | border-radius: 50%; 145 | border: 2px solid rgba(0, 0, 0, 0.55); 146 | padding: 4px; 147 | margin: 0 3px; 148 | transition-timing-function: cubic-bezier(0.17, 0.67, 0.83, 0.67); 149 | transition: 0.4s; 150 | &.active { 151 | background: #6b6b6b; 152 | transform: scale(1.2); 153 | } 154 | } 155 | } 156 | 157 | .carouselHoved { 158 | opacity: 0.4; 159 | } 160 | 161 | .myBannerPoint { 162 | position: absolute; 163 | display: -webkit-box; 164 | display: -ms-flexbox; 165 | display: flex; 166 | width: 100%; 167 | left: 0; 168 | bottom: 0; 169 | padding: 0; 170 | color: beige; 171 | justify-content: center; 172 | list-style-type: none; 173 | background: rgba(0, 0, 0, 0.34); 174 | margin: 0; 175 | padding: 10px; 176 | box-sizing: border-box; 177 | li { 178 | background: #6b6b6b; 179 | width: 50px; 180 | height: 50px; 181 | margin-right: 10px; 182 | &.active { 183 | transform: translateY(-10px); 184 | transition: 0.3s ease all; 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/tile/tile.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { NguCarousel } from '@ngu/carousel'; 3 | 4 | import { TileComponent } from './tile.component'; 5 | 6 | describe('TileComponent', () => { 7 | let component: TileComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(waitForAsync(() => { 11 | TestBed.configureTestingModule({ 12 | imports: [NguCarousel, TileComponent] 13 | }).compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(TileComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/tile/tile.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, afterNextRender, signal } from '@angular/core'; 2 | import { NguCarouselConfig } from '@ngu/carousel'; 3 | import { slider } from '../slide-animation'; 4 | import { 5 | NguTileComponent, 6 | NguCarouselPrevDirective, 7 | NguCarouselDefDirective, 8 | NguCarouselNextDirective, 9 | NguCarouselPointDirective, 10 | NguCarousel 11 | } from '@ngu/carousel'; 12 | 13 | @Component({ 14 | selector: 'app-tile', 15 | templateUrl: './tile.component.html', 16 | styleUrls: ['./tile.component.scss'], 17 | changeDetection: ChangeDetectionStrategy.OnPush, 18 | animations: [slider], 19 | imports: [ 20 | NguCarousel, 21 | NguCarouselPrevDirective, 22 | NguCarouselDefDirective, 23 | NguTileComponent, 24 | NguCarouselNextDirective, 25 | NguCarouselPointDirective 26 | ] 27 | }) 28 | export class TileComponent { 29 | images = ['assets/bg.jpg', 'assets/car.png', 'assets/canberra.jpg', 'assets/holi.jpg']; 30 | 31 | public carouselItems = signal([]); 32 | public carouselTileConfig: NguCarouselConfig = { 33 | grid: { xs: 1, sm: 1, md: 1, lg: 5, all: 0 }, 34 | speed: 250, 35 | point: { 36 | visible: true 37 | }, 38 | touch: true, 39 | loop: true, 40 | interval: { timing: 1500 }, 41 | animation: 'lazy' 42 | }; 43 | 44 | constructor() { 45 | this.addItem(); 46 | afterNextRender(() => { 47 | const id = setInterval(() => { 48 | this.addItem(); 49 | if (this.carouselItems().length >= 30) { 50 | clearInterval(id); 51 | } 52 | }, 500); 53 | }); 54 | } 55 | 56 | private addItem() { 57 | const i = Math.floor(Math.random() * this.images.length); 58 | this.carouselItems.update(items => [...items, this.images[i]]); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/wrapped/wrapped-carousel/wrapped-carousel.component.html: -------------------------------------------------------------------------------- 1 | @if (items().length) { 2 |
3 |

Wrapped Carousel

4 | 5 | 8 | 9 | 10 | @if (i % 2 === 0) { 11 | 14 | } @else { 15 |
16 |

Odd: {{ i + 1 }}

17 |
18 | } 19 |
20 | 21 | 24 | 25 |
    26 | @for (i of myCarousel.pointNumbers(); track i) { 27 |
  • 28 | } 29 |
30 |
31 |
32 | } 33 | 34 | 35 |
36 |

Even: {{ i + 1 }}

37 |
38 |
39 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/wrapped/wrapped-carousel/wrapped-carousel.component.scss: -------------------------------------------------------------------------------- 1 | .bannerStyle div { 2 | background-color: #ccc; 3 | background-size: cover !important; 4 | height: 100%; 5 | } 6 | 7 | .bannerStyle img { 8 | width: 100%; 9 | height: 100%; 10 | } 11 | 12 | .bannerStyle h1 { 13 | text-align: center; 14 | background: rgba(0, 0, 0, 0.32); 15 | color: white; 16 | margin: 0; 17 | height: 100%; 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | } 22 | 23 | .tile { 24 | min-height: 200px; 25 | background-color: #ccc; 26 | background-size: cover !important; 27 | } 28 | 29 | .tile h1 { 30 | text-align: center; 31 | line-height: 200px; 32 | background: rgba(0, 0, 0, 0.32); 33 | color: white; 34 | margin: 0; 35 | } 36 | 37 | h4 { 38 | margin: 0; 39 | padding: 10px 15px; 40 | text-align: center; 41 | } 42 | 43 | p { 44 | margin: 0; 45 | padding: 0 15px 10px; 46 | text-align: center; 47 | } 48 | 49 | .wBg { 50 | background: white; 51 | } 52 | 53 | .container { 54 | max-width: 1200px; 55 | margin: 0 auto; 56 | } 57 | 58 | nav { 59 | border-bottom: 1px solid #ccc; 60 | position: fixed; 61 | width: 100%; 62 | top: 0; 63 | background: white; 64 | z-index: 12; 65 | } 66 | 67 | nav h1 { 68 | margin: 0; 69 | padding: 10px; 70 | text-align: center; 71 | } 72 | 73 | .carBtn { 74 | position: absolute; 75 | margin: auto; 76 | top: 0; 77 | bottom: 0; 78 | width: 50px; 79 | height: 50px; 80 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 81 | border-radius: 999px; 82 | } 83 | 84 | .leftRs { 85 | position: absolute; 86 | margin: auto; 87 | top: 0; 88 | bottom: 0; 89 | width: 50px; 90 | height: 50px; 91 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 92 | border-radius: 999px; 93 | left: 0; 94 | z-index: 1; 95 | } 96 | 97 | .rightRs { 98 | position: absolute; 99 | margin: auto; 100 | top: 0; 101 | bottom: 0; 102 | width: 50px; 103 | height: 50px; 104 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 105 | border-radius: 999px; 106 | right: 0; 107 | z-index: 1; 108 | } 109 | 110 | .leftRs1 { 111 | position: absolute; 112 | margin: auto; 113 | top: 0; 114 | bottom: 0; 115 | width: 50px; 116 | height: 50px; 117 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 118 | border-radius: 999px; 119 | right: 0; 120 | } 121 | 122 | .rightRs1 { 123 | position: absolute; 124 | margin: auto; 125 | top: 0; 126 | bottom: 0; 127 | width: 50px; 128 | height: 50px; 129 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 130 | border-radius: 999px; 131 | left: 0; 132 | } 133 | 134 | .myPoint { 135 | list-style-type: none; 136 | text-align: center; 137 | padding: 12px; 138 | margin: 0; 139 | white-space: nowrap; 140 | overflow: auto; 141 | box-sizing: border-box; 142 | li { 143 | display: inline-block; 144 | border-radius: 50%; 145 | border: 2px solid rgba(0, 0, 0, 0.55); 146 | padding: 4px; 147 | margin: 0 3px; 148 | transition-timing-function: cubic-bezier(0.17, 0.67, 0.83, 0.67); 149 | transition: 0.4s; 150 | &.active { 151 | background: #6b6b6b; 152 | transform: scale(1.2); 153 | } 154 | } 155 | } 156 | 157 | .carouselHoved { 158 | opacity: 0.4; 159 | } 160 | 161 | .myBannerPoint { 162 | position: absolute; 163 | display: -webkit-box; 164 | display: -ms-flexbox; 165 | display: flex; 166 | width: 100%; 167 | left: 0; 168 | bottom: 0; 169 | padding: 0; 170 | color: beige; 171 | justify-content: center; 172 | list-style-type: none; 173 | background: rgba(0, 0, 0, 0.34); 174 | margin: 0; 175 | padding: 10px; 176 | box-sizing: border-box; 177 | li { 178 | background: #6b6b6b; 179 | width: 50px; 180 | height: 50px; 181 | margin-right: 10px; 182 | &.active { 183 | transform: translateY(-10px); 184 | transition: 0.3s ease all; 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/wrapped/wrapped-carousel/wrapped-carousel.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, input } from '@angular/core'; 2 | import { NguCarouselConfig } from '@ngu/carousel'; 3 | import { slider } from '../../slide-animation'; 4 | import { NgTemplateOutlet } from '@angular/common'; 5 | import { 6 | NguTileComponent, 7 | NguCarouselPrevDirective, 8 | NguCarouselDefDirective, 9 | NguCarouselNextDirective, 10 | NguCarousel 11 | } from '@ngu/carousel'; 12 | 13 | @Component({ 14 | selector: 'app-wrapped-carousel', 15 | templateUrl: 'wrapped-carousel.component.html', 16 | styleUrls: ['./wrapped-carousel.component.scss'], 17 | changeDetection: ChangeDetectionStrategy.OnPush, 18 | animations: [slider], 19 | imports: [ 20 | NguCarousel, 21 | NguCarouselPrevDirective, 22 | NguCarouselDefDirective, 23 | NguTileComponent, 24 | NgTemplateOutlet, 25 | NguCarouselNextDirective 26 | ] 27 | }) 28 | export class WrappedCarouselComponent { 29 | items = input([]); 30 | public config: NguCarouselConfig = { 31 | grid: { xs: 1, sm: 2, md: 3, lg: 4, xl: 4, all: 0 }, 32 | gridBreakpoints: { sm: 480, md: 768, lg: 1024, xl: 1280 }, 33 | speed: 750, 34 | point: { 35 | visible: true 36 | }, 37 | velocity: 0.1, 38 | touch: true, 39 | easing: 'cubic-bezier(0, 0, 0.2, 1)' 40 | }; 41 | 42 | default = { 43 | xs: 1, 44 | sm: 2, 45 | md: 3, 46 | lg: 4, 47 | xl: 4, 48 | all: 0 49 | }; 50 | 51 | grid = input(this.default, { 52 | transform: (value: Partial) => { 53 | return value || this.default; 54 | } 55 | }); 56 | } 57 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/app/wrapped/wrapped.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, Signal, signal } from '@angular/core'; 2 | import data from '../../assets/mock/images.json'; 3 | import { WrappedCarouselComponent } from './wrapped-carousel/wrapped-carousel.component'; 4 | 5 | @Component({ 6 | selector: 'app-wrapped', 7 | template: ``, 8 | changeDetection: ChangeDetectionStrategy.OnPush, 9 | imports: [WrappedCarouselComponent] 10 | }) 11 | export class WrappedComponent { 12 | public grid: { xs: 1; sm: 1; md: 1; lg: 5; all: 0 }; 13 | data = signal([]); 14 | items = this.data.asReadonly(); 15 | 16 | constructor() { 17 | setTimeout(() => { 18 | console.log(data.images); 19 | this.data.set(data.images); 20 | }, 1000); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiuniversal/ngu-carousel/735c71291bd3af93980adb6aafc6b701abb808d3/apps/ngu-carousel-example/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/assets/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiuniversal/ngu-carousel/735c71291bd3af93980adb6aafc6b701abb808d3/apps/ngu-carousel-example/src/assets/bg.jpg -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/assets/canberra.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiuniversal/ngu-carousel/735c71291bd3af93980adb6aafc6b701abb808d3/apps/ngu-carousel-example/src/assets/canberra.jpg -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/assets/car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiuniversal/ngu-carousel/735c71291bd3af93980adb6aafc6b701abb808d3/apps/ngu-carousel-example/src/assets/car.png -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/assets/holi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiuniversal/ngu-carousel/735c71291bd3af93980adb6aafc6b701abb808d3/apps/ngu-carousel-example/src/assets/holi.jpg -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/assets/mock/images.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | "assets/bg.jpg", 4 | "assets/car.png" 5 | ] 6 | } -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiuniversal/ngu-carousel/735c71291bd3af93980adb6aafc6b701abb808d3/apps/ngu-carousel-example/src/favicon.ico -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NguCarouselExample 6 | 7 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/main.server.ts: -------------------------------------------------------------------------------- 1 | import { bootstrapApplication } from '@angular/platform-browser'; 2 | import { AppComponent } from './app/app.component'; 3 | import { config } from './app/app-server.config'; 4 | 5 | const bootstrap = () => bootstrapApplication(AppComponent, config); 6 | 7 | export default bootstrap; 8 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrapApplication } from '@angular/platform-browser'; 2 | import { AppComponent } from './app/app.component'; 3 | import { appConfig } from './app/app.config'; 4 | 5 | bootstrapApplication(AppComponent, appConfig).catch(err => console.error(err)); 6 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/styles.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | body { 6 | margin: 0; 7 | font-family: Roboto, 'Helvetica Neue', sans-serif; 8 | } 9 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import { getTestBed } from '@angular/core/testing'; 4 | import { 5 | BrowserDynamicTestingModule, 6 | platformBrowserDynamicTesting 7 | } from '@angular/platform-browser-dynamic/testing'; 8 | 9 | getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), { 10 | errorOnUnknownElements: true, 11 | errorOnUnknownProperties: true 12 | }); 13 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": ["hammerjs"], 6 | "resolveJsonModule": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true 9 | }, 10 | "files": ["src/main.ts", "src/main.server.ts", "server.ts"], 11 | "include": ["src/**/*.d.ts"], 12 | "exclude": ["karma.conf.js", "src/**/*.test.ts", "src/**/*.spec.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022" 4 | }, 5 | "files": [], 6 | "include": [], 7 | "references": [ 8 | { 9 | "path": "./tsconfig.app.json" 10 | }, 11 | { 12 | "path": "./tsconfig.spec.json" 13 | }, 14 | { 15 | "path": "./tsconfig.server.json" 16 | } 17 | ], 18 | "extends": "../../tsconfig.base.json" 19 | } 20 | -------------------------------------------------------------------------------- /apps/ngu-carousel-example/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": ["jasmine", "node"] 6 | }, 7 | "files": ["src/test.ts"], 8 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /decorate-angular-cli.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file decorates the Angular CLI with the Nx CLI to enable features such as computation caching 3 | * and faster execution of tasks. 4 | * 5 | * It does this by: 6 | * 7 | * - Patching the Angular CLI to warn you in case you accidentally use the undecorated ng command. 8 | * - Symlinking the ng to nx command, so all commands run through the Nx CLI 9 | * - Updating the package.json postinstall script to give you control over this script 10 | * 11 | * The Nx CLI decorates the Angular CLI, so the Nx CLI is fully compatible with it. 12 | * Every command you run should work the same when using the Nx CLI, except faster. 13 | * 14 | * Because of symlinking you can still type `ng build/test/lint` in the terminal. The ng command, in this case, 15 | * will point to nx, which will perform optimizations before running your task. 16 | * 17 | * To opt out of this patch: 18 | * - Replace occurrences of nx with ng in your package.json 19 | * - Remove the script from your postinstall script in your package.json 20 | * - Delete and reinstall your node_modules 21 | */ 22 | 23 | const fs = require('fs'); 24 | const os = require('os'); 25 | const cp = require('child_process'); 26 | const isWindows = os.platform() === 'win32'; 27 | let output; 28 | try { 29 | output = require('@nx/workspace').output; 30 | } catch (e) { 31 | console.warn( 32 | 'Angular CLI could not be decorated to enable computation caching. Please ensure @nx/workspace is installed.' 33 | ); 34 | process.exit(0); 35 | } 36 | 37 | /** 38 | * Symlink of ng to nx, so you can keep using `ng build/test/lint` and still 39 | * invoke the Nx CLI and get the benefits of computation caching. 40 | */ 41 | function symlinkNgCLItoNxCLI() { 42 | try { 43 | const ngPath = './node_modules/.bin/ng'; 44 | const nxPath = './node_modules/.bin/nx'; 45 | if (isWindows) { 46 | /** 47 | * This is the most reliable way to create symlink-like behavior on Windows. 48 | * Such that it works in all shells and works with npx. 49 | */ 50 | ['', '.cmd', '.ps1'].forEach(ext => { 51 | if (fs.existsSync(nxPath + ext)) 52 | fs.writeFileSync(ngPath + ext, fs.readFileSync(nxPath + ext)); 53 | }); 54 | } else { 55 | // If unix-based, symlink 56 | cp.execSync(`ln -sf ./nx ${ngPath}`); 57 | } 58 | } catch (e) { 59 | output.error({ 60 | title: 'Unable to create a symlink from the Angular CLI to the Nx CLI:' + e.message 61 | }); 62 | throw e; 63 | } 64 | } 65 | 66 | try { 67 | symlinkNgCLItoNxCLI(); 68 | require('nx/src/adapter/decorate-cli').decorateCli(); 69 | output.log({ title: 'Angular CLI has been decorated to enable computation caching.' }); 70 | } catch (e) { 71 | output.error({ title: 'Decoration of the Angular CLI did not complete successfully' }); 72 | } 73 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | const { join } = require('path'); 5 | const { constants } = require('karma'); 6 | 7 | module.exports = () => { 8 | return { 9 | basePath: '', 10 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 11 | plugins: [ 12 | require('karma-jasmine'), 13 | require('karma-chrome-launcher'), 14 | require('karma-jasmine-html-reporter'), 15 | require('karma-coverage'), 16 | require('@angular-devkit/build-angular/plugins/karma') 17 | ], 18 | client: { 19 | jasmine: { 20 | // you can add configuration options for Jasmine here 21 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 22 | // for example, you can disable the random execution with `random: false` 23 | // or set a specific seed with `seed: 4321` 24 | }, 25 | clearContext: false // leave Jasmine Spec Runner output visible in browser 26 | }, 27 | jasmineHtmlReporter: { 28 | suppressAll: true // removes the duplicated traces 29 | }, 30 | coverageReporter: { 31 | dir: join(__dirname, './coverage'), 32 | subdir: '.', 33 | reporters: [{ type: 'html' }, { type: 'text-summary' }] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: constants.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: true 42 | }; 43 | }; 44 | -------------------------------------------------------------------------------- /libs/ngu/carousel/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../.eslintrc.json", 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "parserOptions": { 8 | "project": [ 9 | "libs/ngu/carousel/tsconfig.*?.json", 10 | "libs/ngu/carousel/.storybook/tsconfig.json", 11 | "libs/ngu/carousel/cypress/tsconfig.json" 12 | ], 13 | "createDefaultProgram": true 14 | }, 15 | "rules": { 16 | "@angular-eslint/directive-selector": [ 17 | "error", 18 | { 19 | "type": "attribute", 20 | "prefix": "Ngu", 21 | "style": "camelCase" 22 | } 23 | ], 24 | "@angular-eslint/component-selector": [ 25 | "error", 26 | { 27 | "type": "element", 28 | "prefix": "ngu", 29 | "style": "kebab-case" 30 | } 31 | ], 32 | "@angular-eslint/directive-class-suffix": "off", 33 | "@angular-eslint/component-class-suffix": "off", 34 | "@angular-eslint/no-host-metadata-property": "off" 35 | } 36 | }, 37 | { 38 | "files": ["*.html"], 39 | "rules": {} 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /libs/ngu/carousel/.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ['../**/*.stories.mdx', '../**/*.stories.@(js|jsx|ts|tsx)'], 3 | addons: ['@storybook/addon-essentials'], 4 | framework: { 5 | name: "@storybook/angular", 6 | options: {} 7 | }, 8 | docs: { 9 | autodocs: true 10 | } 11 | }; 12 | 13 | // To customize your webpack configuration you can use the webpackFinal field. 14 | // Check https://storybook.js.org/docs/react/builders/webpack#extending-storybooks-webpack-config 15 | // and https://nx.dev/packages/storybook/documents/custom-builder-configs -------------------------------------------------------------------------------- /libs/ngu/carousel/.storybook/preview.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiuniversal/ngu-carousel/735c71291bd3af93980adb6aafc6b701abb808d3/libs/ngu/carousel/.storybook/preview.js -------------------------------------------------------------------------------- /libs/ngu/carousel/.storybook/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "emitDecoratorMetadata": true 5 | }, 6 | 7 | "exclude": ["../**/*.spec.ts"], 8 | "include": [ 9 | "../src/**/*.stories.ts", 10 | "../src/**/*.stories.js", 11 | "../src/**/*.stories.jsx", 12 | "../src/**/*.stories.tsx", 13 | "../src/**/*.stories.mdx", 14 | "*.js" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /libs/ngu/carousel/README.md: -------------------------------------------------------------------------------- 1 | # ngu-carousel 2 | 3 | Angular Universal carousel 4 | 5 | `Note: This carousel doesn't include any CSS. go and customize CSS for buttons, items except ngucarousel and ngucarousel-inner` 6 | 7 | ## Demo 8 | 9 | Demo available [Here](https://ngu-carousel.netlify.app/) 10 | 11 | ## Installation 12 | 13 | | Angular Version | ngu-carousel Version | 14 | | ------------------------ | -------------------------------------------- | 15 | | Angular >= 18 | `npm i --save @ngu/carousel@latest` | 16 | | Angular >= 17 | `npm i --save @ngu/carousel@9.0.0` | 17 | | Angular >= 16 standalone | `npm i --save @ngu/carousel@8.0.0` | 18 | | Angular >= 16 | `npm i --save @ngu/carousel@7.2.0` | 19 | | Angular >= 15 | `npm i --save @ngu/carousel@7.0.0` | 20 | | Angular >= 14 | `npm i --save @ngu/carousel@6.0.0` | 21 | | Angular >= 13 | `npm i --save @ngu/carousel@5.0.0` | 22 | | Angular >= 12 | `npm i --save @ngu/carousel@4.0.0` | 23 | | Angular >= 10 | `npm i --save @ngu/carousel@3.0.2` | 24 | | Angular = 9 | `npm i --save @ngu/carousel@2.1.0` | 25 | | Angular < 9 | `npm i --save @ngu/carousel@1.5.5` | 26 | 27 | ## Installation 28 | 29 | `npm install @ngu/carousel --save` 30 | 31 | Include CarouselModule in your app module: 32 | 33 | ```javascript 34 | import { 35 | NguCarousel, 36 | NguCarouselDefDirective, 37 | NguCarouselNextDirective, 38 | NguCarouselPrevDirective, 39 | NguItemComponent 40 | } from '@ngu/carousel'; 41 | 42 | @NgModule({ 43 | imports: [ 44 | NguCarousel, 45 | NguTileComponent, 46 | NguCarousel, 47 | NguCarouselDefDirective, 48 | NguCarouselNextDirective, 49 | NguCarouselPrevDirective, 50 | NguItemComponent 51 | ] 52 | }) 53 | export class AppModule {} 54 | 55 | OR 56 | 57 | @Component({ 58 | imports: [ 59 | NguCarousel, 60 | NguTileComponent, 61 | NguCarousel, 62 | NguCarouselDefDirective, 63 | NguCarouselNextDirective, 64 | NguCarouselPrevDirective, 65 | NguItemComponent 66 | ], 67 | standalone: true 68 | }) 69 | export class AppComponent {} 70 | 71 | ``` 72 | 73 | Now ngu-carousel supports touch with the help of hammerjs 74 | 75 | Import hammerjs in `main.ts` file 76 | 77 | ```javascript 78 | import 'hammerjs'; 79 | ``` 80 | 81 | Then use in your component: 82 | 83 | ```javascript 84 | import { Component, OnInit } from '@angular/core'; 85 | import { NguCarouselConfig } from '@ngu/carousel'; 86 | 87 | @Component({ 88 | selector: 'sample', 89 | template: ` 90 | 91 | 92 | 93 | 94 | 95 |
96 |

{{j}}

97 |
98 |
99 | 100 | 101 |
    102 |
  • 104 |
105 |
106 | 107 |
108 | 109 | 110 |
    111 |
  • 113 |
114 |
115 | 116 | `, 117 | }) 118 | export class SampleComponent implements OnInit { 119 | imgags = [ 120 | 'assets/bg.jpg', 121 | 'assets/car.png', 122 | 'assets/canberra.jpg', 123 | 'assets/holi.jpg' 124 | ]; 125 | public carouselTileItems: Array = [0, 1, 2, 3, 4, 5]; 126 | public carouselTiles = { 127 | 0: [], 128 | 1: [], 129 | 2: [], 130 | 3: [], 131 | 4: [], 132 | 5: [] 133 | }; 134 | public carouselTile: NguCarouselConfig = { 135 | grid: { xs: 1, sm: 1, md: 3, lg: 3, xl:3, all: 0 }, 136 | slide: 3, 137 | speed: 250, 138 | point: { 139 | visible: true 140 | }, 141 | load: 2, 142 | velocity: 0, 143 | touch: true, 144 | easing: 'cubic-bezier(0, 0, 0.2, 1)' 145 | }; 146 | constructor() {} 147 | 148 | ngOnInit() { 149 | this.carouselTileItems.forEach(el => { 150 | this.carouselTileLoad(el); 151 | }); 152 | } 153 | 154 | public carouselTileLoad(j) { 155 | // console.log(this.carouselTiles[j]); 156 | const len = this.carouselTiles[j].length; 157 | if (len <= 30) { 158 | for (let i = len; i < len + 15; i++) { 159 | this.carouselTiles[j].push( 160 | this.imgags[Math.floor(Math.random() * this.imgags.length)] 161 | ); 162 | } 163 | } 164 | } 165 | } 166 | ``` 167 | 168 | ## Input Interface 169 | 170 | ```javascript 171 | export class NguCarouselStore { 172 | type: string; 173 | deviceType: DeviceType; 174 | token: string; 175 | items: number; 176 | load: number; 177 | deviceWidth: number; 178 | carouselWidth: number; 179 | itemWidth: number; 180 | visibleItems: ItemsControl; 181 | slideItems: number; 182 | itemWidthPer: number; 183 | itemLength: number; 184 | currentSlide: number; 185 | easing: string; 186 | speed: number; 187 | transform: Transfrom; 188 | loop: boolean; 189 | dexVal: number; 190 | touchTransform: number; 191 | touch: Touch; 192 | isEnd: boolean; 193 | isFirst: boolean; 194 | isLast: boolean; 195 | RTL: boolean; 196 | vertical: Vertical; 197 | } 198 | export type DeviceType = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'all'; 199 | 200 | export class ItemsControl { 201 | start: number; 202 | end: number; 203 | } 204 | 205 | export class Vertical { 206 | enabled: boolean; 207 | height: number; 208 | } 209 | 210 | export class Touch { 211 | active?: boolean; 212 | swipe: string; 213 | velocity: number; 214 | } 215 | 216 | export class NguCarouselConfig { 217 | grid: Transfrom; 218 | gridBreakpoints?: Breakpoints; 219 | slide?: number; 220 | speed?: number; 221 | interval?: CarouselInterval; 222 | animation?: Animate; 223 | point?: Point; 224 | type?: string; 225 | load?: number; 226 | custom?: Custom; 227 | loop?: boolean; 228 | touch?: boolean; 229 | easing?: string; 230 | RTL?: boolean; 231 | button?: NguButton; 232 | vertical?: Vertical; 233 | velocity?: number; 234 | } 235 | 236 | export class Grid { 237 | xs: number; 238 | sm: number; 239 | md: number; 240 | lg: number; 241 | xl: number; 242 | all: number; 243 | } 244 | 245 | export interface Point { 246 | visible: boolean; 247 | hideOnSingleSlide?: boolean; 248 | } 249 | 250 | export type Custom = 'banner'; 251 | export type Animate = 'lazy'; 252 | ``` 253 | 254 | | Command | Type | Required | Description | 255 | | ------------------------- | ------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | 256 | | `grid` | Object | Yes | **xs** - mobile, **sm** - tablet, **md** - desktop, **lg** - large desktops, **xl** - extra large desktops, **all** - fixed width (When you use **all** make others 0 and vice versa) | 257 | | `gridBreakpoints` | Object | optional | Determines the browser width in pixels that the grid displays the intended number of tiles.

default: `{sm: 768, md: 992, lg: 1200, xl: 1200}` | 258 | | `slide` | number | optional | It is used to slide the number items on click | 259 | | `speed` | milliseconds | optional | It is used for time taken to slide the number items | 260 | | `interval` | milliseconds | optional | It is used to make the carousel auto slide with given value. interval defines the interval between slides | 261 | | `load` | number | optional | It is used to load the items similar to pagination. The carousel will trigger the carouselLoad function to load another set of items. It will help you to improve the performance of the app.**`(carouselLoad)="myfunc($event)"`** | 262 | | `point.visible` | boolean | optional | It is used to indicate no. of slides and also shows the current active slide. | 263 | | `point.hideOnSingleSlide` | boolean | optional | It is used to hide the point indicator when slide is less than one. | 264 | | `touch` | boolean | optional | It is used to active touch support to the carousel. | 265 | | `easing` | string | optional | It is used to define the easing style of the carousel. Only define the ease name without any timing like `ease`,`ease-in` | 266 | | `loop` | boolean | optional | It is used to loop the `ngu-item | ngu-tile`. It must be true for `interval` | 267 | | `animation` | string | optional | It is used to animate the sliding items. currently it only supports `lazy`. more coming soon and also with custom CSS animation option | 268 | | `custom` | string | optional | It is you to define the purpose of the carousel. Currently, it only supports `banner`. | 269 | | `RTL` | boolean | optional | This option enables the `rtl` direction and acts as rtl. By default it is set to `ltr` | 270 | | `vertical.enabled` | boolean | optional | This option enables the `vertical` direction | 271 | | `vertical.height` | number | optional | This option is used to set the height of the carousel | 272 | 273 | ### Custom CSS for Point 274 | 275 | ```html 276 |
    277 |
  • 278 |
279 | ``` 280 | 281 | This is HTML I'm using in the carousel. Add your own CSS according to this elements in `pointStyles`. check below guide for more details. 282 | 283 | ```html 284 | 285 | 286 | ``` 287 | 288 | - `inputs` is an `Input` and It accepts `NguCarouselConfig`. 289 | - `onMove` is an `Output` which triggered on every slide before start and it will emit `$event` as `NguCarouselStore` object. 290 | - `carouselLoad` is an `Output` which triggered when slide reaches the end on items based on inputs `load`. 291 | 292 | # Getstarted guide 293 | 294 | ## Banner with Custom point style 295 | 296 | ```javascript 297 | import { Component } from '@angular/core'; 298 | import { NguCarousel, NguCarouselStore } from '@ngu/carousel'; 299 | 300 | @Component({ 301 | selector: 'app-carousel', 302 | template: ` 303 | 304 | 305 |

1

306 |
307 | 308 | 309 |

2

310 |
311 | 312 | 313 |

3

314 |
315 | 316 | 317 | 318 |
319 | `, 320 | styles: [ 321 | ` 322 | .bannerStyle h1 { 323 | background-color: #ccc; 324 | min-height: 300px; 325 | text-align: center; 326 | line-height: 300px; 327 | } 328 | .leftRs { 329 | position: absolute; 330 | margin: auto; 331 | top: 0; 332 | bottom: 0; 333 | width: 50px; 334 | height: 50px; 335 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 336 | border-radius: 999px; 337 | left: 0; 338 | } 339 | 340 | .rightRs { 341 | position: absolute; 342 | margin: auto; 343 | top: 0; 344 | bottom: 0; 345 | width: 50px; 346 | height: 50px; 347 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 348 | border-radius: 999px; 349 | right: 0; 350 | } 351 | ` 352 | ] 353 | }) 354 | export class Sample implements OnInit { 355 | ngOnInit() { 356 | this.carouselBanner = { 357 | grid: { xs: 1, sm: 1, md: 1, lg: 1, xl: 1, all: 0 }, 358 | slide: 1, 359 | speed: 400, 360 | interval: { 361 | timing: 3000, 362 | initialDelay: 1000 363 | }, 364 | point: { 365 | visible: true 366 | }, 367 | load: 2, 368 | loop: true, 369 | touch: true 370 | }; 371 | } 372 | 373 | /* It will be triggered on every slide*/ 374 | onmoveFn(data: NguCarouselStore) { 375 | console.log(data); 376 | } 377 | } 378 | ``` 379 | 380 | ## Banner with Vertical carousel 381 | 382 | ```javascript 383 | import { Component } from '@angular/core'; 384 | import { NguCarousel, NguCarouselConfig } from '@ngu/carousel'; 385 | 386 | @Component({ 387 | selector: 'app-carousel', 388 | template: ` 389 | 390 | 391 |

1

392 |
393 | 394 | 395 |

2

396 |
397 | 398 | 399 |

3

400 |
401 | 402 | 403 | 404 |
405 | `, 406 | styles: [ 407 | ` 408 | .bannerStyle h1 { 409 | background-color: #ccc; 410 | min-height: 300px; 411 | text-align: center; 412 | line-height: 300px; 413 | } 414 | .leftRs { 415 | position: absolute; 416 | margin: auto; 417 | top: 0; 418 | bottom: 0; 419 | width: 50px; 420 | height: 50px; 421 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 422 | border-radius: 999px; 423 | left: 0; 424 | } 425 | 426 | .rightRs { 427 | position: absolute; 428 | margin: auto; 429 | top: 0; 430 | bottom: 0; 431 | width: 50px; 432 | height: 50px; 433 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, 0.3); 434 | border-radius: 999px; 435 | right: 0; 436 | } 437 | 438 | .ngucarouselPoint { 439 | list-style-type: none; 440 | text-align: center; 441 | padding: 12px; 442 | margin: 0; 443 | white-space: nowrap; 444 | overflow: auto; 445 | position: absolute; 446 | width: 100%; 447 | bottom: 20px; 448 | left: 0; 449 | box-sizing: border-box; 450 | } 451 | .ngucarouselPoint li { 452 | display: inline-block; 453 | border-radius: 999px; 454 | background: rgba(255, 255, 255, 0.55); 455 | padding: 5px; 456 | margin: 0 3px; 457 | transition: 0.4s ease all; 458 | } 459 | .ngucarouselPoint li.active { 460 | background: white; 461 | width: 10px; 462 | } 463 | ` 464 | ] 465 | }) 466 | export class Sample implements OnInit { 467 | ngOnInit() { 468 | this.carouselBanner = { 469 | grid: { xs: 1, sm: 1, md: 1, lg: 1, xl: 1, all: 0 }, 470 | slide: 1, 471 | speed: 400, 472 | interval: 4000, 473 | point: { 474 | visible: true 475 | }, 476 | load: 2, 477 | loop: true, 478 | touch: true, // touch is not currently in active for vertical carousel, will enable it in future build 479 | vertical: { 480 | enabled: true, 481 | height: 400 482 | } 483 | }; 484 | } 485 | 486 | /* It will be triggered on every slide*/ 487 | onmoveFn(data: NguCarousel) { 488 | console.log(data); 489 | } 490 | } 491 | ``` 492 | 493 | ## Tile with Carousel Control 494 | 495 | ```javascript 496 | import { Component } from '@angular/core'; 497 | import { NguCarousel, NguCarouselConfig } from '@ngu/carousel'; 498 | 499 | @Component({ 500 | selector: 'app-carousel', 501 | template: ` 502 | 505 | 506 | 507 |

{{Tile + 1}}

508 |
509 | 510 | 511 | 512 |
513 | 514 | `, 515 | styles: [` 516 | 517 | h1{ 518 | min-height: 200px; 519 | background-color: #ccc; 520 | text-align: center; 521 | line-height: 200px; 522 | } 523 | .leftRs { 524 | position: absolute; 525 | margin: auto; 526 | top: 0; 527 | bottom: 0; 528 | width: 50px; 529 | height: 50px; 530 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, .3); 531 | border-radius: 999px; 532 | left: 0; 533 | } 534 | 535 | .rightRs { 536 | position: absolute; 537 | margin: auto; 538 | top: 0; 539 | bottom: 0; 540 | width: 50px; 541 | height: 50px; 542 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, .3); 543 | border-radius: 999px; 544 | right: 0; 545 | } 546 | `] 547 | }) 548 | export class Sample implements OnInit { 549 | private carouselToken: string; 550 | 551 | public carouselTileItems: Array; 552 | public carouselTile: NguCarouselConfig; 553 | @ViewChild('carousel') carousel: NguCarousel; 554 | 555 | constructor() { } 556 | 557 | ngOnInit(){ 558 | this.carouselTileItems = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; 559 | 560 | this.carouselTile = { 561 | grid: {xs: 2, sm: 3, md: 3, lg: 5, xl:5, all: 0}, 562 | slide: 2, 563 | speed: 400, 564 | animation: 'lazy', 565 | point: { 566 | visible: true 567 | }, 568 | load: 2, 569 | touch: true, 570 | easing: 'ease' 571 | } 572 | } 573 | 574 | initDataFn(key: NguCarouselStore) { 575 | this.carouselToken = key.token; 576 | } 577 | 578 | resetFn() { 579 | this.carousel.reset(this.carouselToken); 580 | } 581 | 582 | moveToSlide() { 583 | this.carousel.moveToSlide(this.carouselToken, 2, false); 584 | } 585 | 586 | public carouselTileLoad(evt: any) { 587 | 588 | const len = this.carouselTileItems.length 589 | if (len <= 30) { 590 | for (let i = len; i < len + 10; i++) { 591 | this.carouselTileItems.push(i); 592 | } 593 | } 594 | 595 | } 596 | 597 | // carouselLoad will trigger this function when your load value reaches 598 | // it helps to load the data by parts to increase the performance of the app 599 | // must use feature to all carousel 600 | 601 | } 602 | ``` 603 | 604 | ## Tile with custom point style 605 | 606 | ```javascript 607 | import { Component } from '@angular/core'; 608 | import { NguCarousel } from '@ngu/carousel'; 609 | 610 | @Component({ 611 | selector: 'app-carousel', 612 | template: ` 613 | 616 | 617 | 618 |

{{Tile + 1}}

619 |
620 | 621 | 622 | 623 |
624 | `, 625 | styles: [` 626 | 627 | h1{ 628 | min-height: 200px; 629 | background-color: #ccc; 630 | text-align: center; 631 | line-height: 200px; 632 | } 633 | .leftRs { 634 | position: absolute; 635 | margin: auto; 636 | top: 0; 637 | bottom: 0; 638 | width: 50px; 639 | height: 50px; 640 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, .3); 641 | border-radius: 999px; 642 | left: 0; 643 | } 644 | 645 | .rightRs { 646 | position: absolute; 647 | margin: auto; 648 | top: 0; 649 | bottom: 0; 650 | width: 50px; 651 | height: 50px; 652 | box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, .3); 653 | border-radius: 999px; 654 | right: 0; 655 | } 656 | `] 657 | }) 658 | export class Sample implements OnInit { 659 | 660 | public carouselTileItems: Array; 661 | public carouselTile: NguCarousel; 662 | 663 | ngOnInit(){ 664 | this.carouselTileItems = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; 665 | 666 | this.carouselTile = { 667 | grid: {xs: 2, sm: 3, md: 3, lg: 5, xl:5, all: 0}, 668 | slide: 2, 669 | speed: 400, 670 | animation: 'lazy', 671 | point: { 672 | visible: true, 673 | pointStyles: ` 674 | .ngucarouselPoint { 675 | list-style-type: none; 676 | text-align: center; 677 | padding: 12px; 678 | margin: 0; 679 | white-space: nowrap; 680 | overflow: auto; 681 | box-sizing: border-box; 682 | } 683 | .ngucarouselPoint li { 684 | display: inline-block; 685 | border-radius: 50%; 686 | border: 2px solid rgba(0, 0, 0, 0.55); 687 | padding: 4px; 688 | margin: 0 3px; 689 | transition-timing-function: cubic-bezier(.17, .67, .83, .67); 690 | transition: .4s; 691 | } 692 | .ngucarouselPoint li.active { 693 | background: #6b6b6b; 694 | transform: scale(1.2); 695 | } 696 | ` 697 | }, 698 | load: 2, 699 | touch: true, 700 | easing: 'ease' 701 | } 702 | } 703 | 704 | public carouselTileLoad(evt: any) { 705 | 706 | const len = this.carouselTileItems.length 707 | if (len <= 30) { 708 | for (let i = len; i < len + 10; i++) { 709 | this.carouselTileItems.push(i); 710 | } 711 | } 712 | 713 | } 714 | 715 | // carouselLoad will trigger this function when your load value reaches 716 | // it helps to load the data by parts to increase the performance of the app 717 | // must use feature to all carousel 718 | 719 | } 720 | ``` 721 | 722 | ## License 723 | 724 | [MIT](LICENSE) license. 725 | -------------------------------------------------------------------------------- /libs/ngu/carousel/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress'; 2 | 3 | export default defineConfig({}); 4 | -------------------------------------------------------------------------------- /libs/ngu/carousel/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /libs/ngu/carousel/cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************** 3 | // This example commands.ts shows you how to 4 | // create various custom commands and overwrite 5 | // existing commands. 6 | // 7 | // For more comprehensive examples of custom 8 | // commands please read more here: 9 | // https://on.cypress.io/custom-commands 10 | // *********************************************** 11 | 12 | declare namespace Cypress { 13 | interface Chainable { 14 | login(email: string, password: string): void; 15 | } 16 | } 17 | // 18 | // -- This is a parent command -- 19 | Cypress.Commands.add('login', (email, password) => { 20 | console.log('Custom command example: Login', email, password); 21 | }); 22 | // 23 | // -- This is a child command -- 24 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 25 | // 26 | // 27 | // -- This is a dual command -- 28 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 29 | // 30 | // 31 | // -- This will overwrite an existing command -- 32 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 33 | -------------------------------------------------------------------------------- /libs/ngu/carousel/cypress/support/component-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | @ngu/carousel Components App 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /libs/ngu/carousel/cypress/support/component.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/component.ts is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.ts using ES2015 syntax: 17 | import './commands'; 18 | -------------------------------------------------------------------------------- /libs/ngu/carousel/cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["cypress", "node"], 7 | "sourceMap": false 8 | }, 9 | "include": [ 10 | "support/**/*.ts", 11 | "../cypress.config.ts", 12 | "../**/*.cy.ts", 13 | "../**/*.cy.tsx", 14 | "../**/*.cy.js", 15 | "../**/*.cy.jsx", 16 | "../**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /libs/ngu/carousel/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | const path = require('path'); 5 | 6 | module.exports = function (config) { 7 | config.set({ 8 | basePath: '', 9 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 10 | plugins: [ 11 | require('karma-jasmine'), 12 | require('karma-chrome-launcher'), 13 | require('karma-coverage'), 14 | require('karma-jasmine-html-reporter'), 15 | require('@angular-devkit/build-angular/plugins/karma') 16 | ], 17 | client: { 18 | clearContext: false // leave Jasmine Spec Runner output visible in browser 19 | }, 20 | coverageReporter: { 21 | dir: path.join(__dirname, 'reports', 'coverage'), 22 | subdir: '.', 23 | reporters: [{ type: 'lcov' }] 24 | }, 25 | reporters: ['progress', 'kjhtml', 'coverage'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome'], 31 | customLaunchers: { 32 | ChromeHeadlessCI: { 33 | base: 'ChromeHeadless', 34 | flags: ['--no-sandbox', '--disable-gpu'] 35 | } 36 | }, 37 | singleRun: false, 38 | restartOnFileChange: true 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /libs/ngu/carousel/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../../dist/libs/ngu/carousel", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /libs/ngu/carousel/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngu/carousel", 3 | "version": "19.0.0", 4 | "peerDependencies": { 5 | "@angular/common": "^19.0.0", 6 | "@angular/core": "^19.0.0", 7 | "@angular/animations": "^19.0.0", 8 | "hammerjs": "^2.0.0", 9 | "rxjs": "^7.0.0" 10 | }, 11 | "dependencies": { 12 | "tslib": "^2.6.0" 13 | }, 14 | "license": "MIT", 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/uiuniversal/ngu-carousel.git" 18 | }, 19 | "keywords": [ 20 | "angular", 21 | "carousel", 22 | "universal", 23 | "angular universal", 24 | "universal carousel", 25 | "ngu carousel" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /libs/ngu/carousel/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngu/carousel", 3 | "$schema": "../../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "library", 5 | "sourceRoot": "libs/ngu/carousel/src", 6 | "prefix": "ngu", 7 | "targets": { 8 | "build": { 9 | "executor": "@nx/angular:package", 10 | "options": { 11 | "project": "libs/ngu/carousel/ng-package.json" 12 | }, 13 | "configurations": { 14 | "production": { 15 | "tsConfig": "libs/ngu/carousel/tsconfig.lib.prod.json" 16 | }, 17 | "development": { 18 | "tsConfig": "libs/ngu/carousel/tsconfig.lib.json" 19 | } 20 | }, 21 | "defaultConfiguration": "production" 22 | }, 23 | "test": { 24 | "executor": "@angular-devkit/build-angular:karma", 25 | "options": { 26 | "main": "libs/ngu/carousel/src/test.ts", 27 | "tsConfig": "libs/ngu/carousel/tsconfig.spec.json", 28 | "karmaConfig": "libs/ngu/carousel/karma.conf.js", 29 | "polyfills": ["zone.js", "zone.js/testing"] 30 | } 31 | }, 32 | "lint": { 33 | "executor": "@nx/eslint:lint", 34 | "options": { 35 | "lintFilePatterns": ["{projectRoot}/**/*.ts", "{projectRoot}/**/*.html"] 36 | } 37 | }, 38 | "component-test": { 39 | "executor": "@nx/cypress:cypress", 40 | "options": { 41 | "cypressConfig": "libs/ngu/carousel/cypress.config.ts", 42 | "testingType": "component" 43 | } 44 | }, 45 | "storybook": { 46 | "executor": "@storybook/angular:start-storybook", 47 | "options": { 48 | "port": 4400, 49 | "configDir": "libs/ngu/carousel/.storybook", 50 | "browserTarget": "@ngu/carousel:build-storybook", 51 | "compodoc": false 52 | }, 53 | "configurations": { 54 | "ci": { 55 | "quiet": true 56 | } 57 | } 58 | }, 59 | "build-storybook": { 60 | "executor": "@storybook/angular:build-storybook", 61 | "outputs": ["{options.outputDir}"], 62 | "options": { 63 | "outputDir": "dist/storybook/@ngu/carousel", 64 | "configDir": "libs/ngu/carousel/.storybook", 65 | "browserTarget": "@ngu/carousel:build-storybook", 66 | "compodoc": false 67 | }, 68 | "configurations": { 69 | "ci": { 70 | "quiet": true 71 | } 72 | } 73 | }, 74 | "static-storybook": { 75 | "executor": "@nx/web:file-server", 76 | "options": { 77 | "buildTarget": "@ngu/carousel:build-storybook", 78 | "staticFilePath": "dist/storybook/@ngu/carousel" 79 | }, 80 | "configurations": { 81 | "ci": { 82 | "buildTarget": "@ngu/carousel:build-storybook:ci" 83 | } 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /libs/ngu/carousel/src/lib/carousel-animation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | animate, 3 | AnimationTriggerMetadata, 4 | state, 5 | style, 6 | transition, 7 | trigger 8 | } from '@angular/animations'; 9 | 10 | /** Time and timing curve for expansion panel animations. */ 11 | export const EXPANSION_PANEL_ANIMATION_TIMING = '225ms cubic-bezier(0.4,0.0,0.2,1)'; 12 | 13 | /** Animations used by the Material expansion panel. */ 14 | export const slider: AnimationTriggerMetadata = trigger('slider', [ 15 | state('rtl', style({ transform: 'translate3d({{distance}}%,0,0)' }), { 16 | params: { distance: '0' } 17 | }), 18 | state('withoutRTL', style({ transform: 'translate3d({{distance}}%,0,0)' }), { 19 | params: { distance: '0' } 20 | }), 21 | transition('* => *', animate('200ms ease-in')) 22 | ]); 23 | -------------------------------------------------------------------------------- /libs/ngu/carousel/src/lib/ngu-carousel.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, TemplateRef, ViewContainerRef, inject } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[NguCarouselItem]' 5 | }) 6 | export class NguCarouselItemDirective {} 7 | 8 | @Directive({ 9 | selector: '[NguCarouselNext]' 10 | }) 11 | export class NguCarouselNextDirective {} 12 | 13 | @Directive({ 14 | selector: '[NguCarouselPrev]' 15 | }) 16 | export class NguCarouselPrevDirective {} 17 | 18 | @Directive({ 19 | selector: '[NguCarouselPoint]' 20 | }) 21 | export class NguCarouselPointDirective {} 22 | 23 | @Directive({ 24 | // eslint-disable-next-line @angular-eslint/directive-selector 25 | selector: '[nguCarouselDef]' 26 | }) 27 | export class NguCarouselDefDirective { 28 | template = inject(TemplateRef); 29 | when?: (index: number, nodeData: T) => boolean; 30 | } 31 | 32 | @Directive({ 33 | // eslint-disable-next-line @angular-eslint/directive-selector 34 | selector: '[nguCarouselOutlet]' 35 | }) 36 | export class NguCarouselOutlet { 37 | viewContainer = inject(ViewContainerRef); 38 | } 39 | -------------------------------------------------------------------------------- /libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel-hammer-manager.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NgZone, OnDestroy } from '@angular/core'; 2 | import { Observable, Subject, defer, fromEvent, map, shareReplay, takeUntil } from 'rxjs'; 3 | 4 | @Injectable({ providedIn: 'root' }) 5 | export class NguHammerLoader { 6 | private _hammer$ = defer(() => import('hammerjs')).pipe( 7 | shareReplay({ bufferSize: 1, refCount: true }) 8 | ); 9 | 10 | load() { 11 | return this._hammer$; 12 | } 13 | } 14 | 15 | @Injectable() 16 | export class NguCarouselHammerManager implements OnDestroy { 17 | private _destroy$ = new Subject(); 18 | 19 | constructor(private _ngZone: NgZone, private _nguHammerLoader: NguHammerLoader) {} 20 | 21 | ngOnDestroy(): void { 22 | this._destroy$.next(); 23 | } 24 | 25 | createHammer(element: HTMLElement): Observable { 26 | return this._nguHammerLoader.load().pipe( 27 | map(() => 28 | // Note: The Hammer manager should be created outside of the Angular zone since it sets up 29 | // `pointermove` event listener which triggers change detection every time the pointer is moved. 30 | this._ngZone.runOutsideAngular(() => new Hammer(element)) 31 | ), 32 | // Note: the dynamic import is always a microtask which may run after the view is destroyed. 33 | // `takeUntil` is used to prevent setting Hammer up if the view had been destroyed before 34 | // the HammerJS is loaded. 35 | takeUntil(this._destroy$) 36 | ); 37 | } 38 | 39 | on(hammer: HammerManager, event: string) { 40 | return fromEvent(hammer, event).pipe( 41 | // Note: We have to re-enter the Angular zone because Hammer would trigger events outside of the 42 | // Angular zone (since we set it up with `runOutsideAngular`). 43 | enterNgZone(this._ngZone), 44 | takeUntil(this._destroy$) 45 | ); 46 | } 47 | } 48 | 49 | function enterNgZone(ngZone: NgZone) { 50 | return (source: Observable) => 51 | new Observable(subscriber => 52 | source.subscribe({ 53 | next: value => ngZone.run(() => subscriber.next(value)), 54 | error: error => ngZone.run(() => subscriber.error(error)), 55 | complete: () => ngZone.run(() => subscriber.complete()) 56 | }) 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 | 7 |
8 |
9 |
10 |
11 | 12 |
13 | 14 | -------------------------------------------------------------------------------- /libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | position: relative; 4 | &.ngurtl { 5 | direction: rtl; 6 | } 7 | } 8 | 9 | .ngucarousel { 10 | // height: 100%; 11 | // .ngucarousel-inner { 12 | position: relative; 13 | overflow: hidden; 14 | height: 100%; 15 | .ngucarousel-items { 16 | position: relative; 17 | display: flex; 18 | height: 100%; 19 | } // } 20 | } 21 | 22 | .ngu-container { 23 | overflow: hidden; 24 | } 25 | 26 | .nguvertical { 27 | flex-direction: column; 28 | } 29 | 30 | .banner { 31 | .ngucarouselPointDefault { 32 | .ngucarouselPoint { 33 | position: absolute; 34 | width: 100%; 35 | bottom: 20px; 36 | li { 37 | background: rgba(255, 255, 255, 0.55); 38 | &.active { 39 | background: white; 40 | } 41 | &:hover { 42 | cursor: pointer; 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | .ngucarouselPointDefault { 50 | .ngucarouselPoint { 51 | list-style-type: none; 52 | text-align: center; 53 | padding: 12px; 54 | margin: 0; 55 | white-space: nowrap; 56 | overflow: auto; 57 | box-sizing: border-box; 58 | li { 59 | display: inline-block; 60 | border-radius: 50%; 61 | background: rgba(0, 0, 0, 0.55); 62 | padding: 4px; 63 | margin: 0 4px; 64 | transition-timing-function: cubic-bezier(0.17, 0.67, 0.83, 0.67); 65 | transition: 0.4s; 66 | &.active { 67 | background: #6b6b6b; 68 | transform: scale(1.8); 69 | } 70 | &:hover { 71 | cursor: pointer; 72 | } 73 | } 74 | } 75 | } 76 | 77 | .nguclearFix { 78 | clear: both; 79 | } 80 | -------------------------------------------------------------------------------- /libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChangeDetectionStrategy, 3 | Component, 4 | ElementRef, 5 | IterableChangeRecord, 6 | IterableChanges, 7 | IterableDiffer, 8 | IterableDiffers, 9 | NgIterable, 10 | NgZone, 11 | OnDestroy, 12 | Renderer2, 13 | TrackByFunction, 14 | computed, 15 | contentChild, 16 | contentChildren, 17 | effect, 18 | inject, 19 | input, 20 | output, 21 | viewChild, 22 | signal, 23 | untracked, 24 | ChangeDetectorRef, 25 | afterNextRender, 26 | AfterRenderPhase 27 | } from '@angular/core'; 28 | import { EMPTY, Subject, fromEvent, interval, merge, timer } from 'rxjs'; 29 | import { debounceTime, filter, map, startWith, switchMap, takeUntil } from 'rxjs/operators'; 30 | 31 | import { IS_BROWSER } from '../symbols'; 32 | import { 33 | NguCarouselDefDirective, 34 | NguCarouselNextDirective, 35 | NguCarouselOutlet, 36 | NguCarouselPrevDirective 37 | } from '../ngu-carousel.directive'; 38 | import { 39 | Breakpoints, 40 | NguCarouselConfig, 41 | NguCarouselOutletContext, 42 | NguCarouselStore, 43 | Transfrom 44 | } from './ngu-carousel'; 45 | import { NguCarouselHammerManager } from './ngu-carousel-hammer-manager'; 46 | import { NguWindowScrollListener } from './ngu-window-scroll-listener'; 47 | 48 | type DirectionSymbol = '' | '-'; 49 | 50 | type NguCarouselDataSource = (U & NgIterable) | null | undefined; 51 | 52 | // This will be provided through Terser global definitions by Angular CLI. 53 | // This is how Angular does tree-shaking internally. 54 | declare const ngDevMode: boolean; 55 | 56 | const NG_DEV_MODE = typeof ngDevMode === 'undefined' || ngDevMode; 57 | 58 | @Component({ 59 | selector: 'ngu-carousel', 60 | templateUrl: 'ngu-carousel.component.html', 61 | styleUrls: ['ngu-carousel.component.scss'], 62 | changeDetection: ChangeDetectionStrategy.OnPush, 63 | providers: [NguCarouselHammerManager], 64 | imports: [NguCarouselOutlet] 65 | }) 66 | export class NguCarousel = NgIterable> 67 | extends NguCarouselStore 68 | implements OnDestroy 69 | { 70 | private _host = inject>(ElementRef); 71 | private _renderer = inject(Renderer2); 72 | private _differs = inject(IterableDiffers); 73 | private _isBrowser = inject(IS_BROWSER); 74 | private _ngZone = inject(NgZone); 75 | private _nguWindowScrollListener = inject(NguWindowScrollListener); 76 | private _nguCarouselHammerManager = inject(NguCarouselHammerManager); 77 | private _cdr = inject(ChangeDetectorRef); 78 | /** Public property that may be accessed outside of the component. */ 79 | readonly activePoint = signal(0); 80 | readonly pointNumbers = signal([]); 81 | 82 | readonly inputs = input.required(); 83 | readonly carouselLoad = output(); 84 | readonly onMove = output(); 85 | 86 | private _defDirectives = contentChildren(NguCarouselDefDirective); 87 | 88 | private _nodeOutlet = viewChild.required(NguCarouselOutlet); 89 | 90 | readonly nextButton = contentChild(NguCarouselNextDirective, { read: ElementRef }); 91 | readonly prevButton = contentChild(NguCarouselPrevDirective, { read: ElementRef }); 92 | 93 | readonly carouselMain1 = viewChild.required('ngucarousel', { read: ElementRef }); 94 | readonly _nguItemsContainer = viewChild.required('nguItemsContainer', { read: ElementRef }); 95 | readonly _touchContainer = viewChild.required('touchContainer', { read: ElementRef }); 96 | 97 | private _arrayChanges: IterableChanges | null = null; 98 | 99 | readonly dataSource = input.required({ 100 | transform: (v: NguCarouselDataSource) => v || ([] as never) 101 | }); 102 | 103 | private _intervalController$ = new Subject(); 104 | 105 | private _hammer: HammerManager | null = null; 106 | 107 | private _withAnimation = true; 108 | 109 | private _directionSymbol: DirectionSymbol; 110 | 111 | private _carouselCssNode: HTMLStyleElement; 112 | 113 | private _dataDiffer: IterableDiffer; 114 | 115 | private _styleid: string; 116 | 117 | private _pointIndex: number; 118 | 119 | private _destroy$ = new Subject(); 120 | 121 | /** 122 | * Tracking function that will be used to check the differences in data changes. Used similarly 123 | * to `ngFor` `trackBy` function. Optimize Items operations by identifying a Items based on its data 124 | * relative to the function to know if a Items should be added/removed/moved. 125 | * Accepts a function that takes two parameters, `index` and `item`. 126 | */ 127 | readonly trackBy = input>(); 128 | readonly _trackByFn = computed(() => { 129 | const fn = this.trackBy(); 130 | if (NG_DEV_MODE && fn != null && typeof fn !== 'function' && console?.warn) { 131 | console.warn(`trackBy must be a function, but received ${JSON.stringify(fn)}.`); 132 | } 133 | return fn || ((index: number, item: T) => item); 134 | }); 135 | 136 | constructor() { 137 | super(); 138 | this._dataDiffer = this._differs.find([]).create(this._trackByFn())!; 139 | 140 | afterNextRender( 141 | () => { 142 | this._inputValidation(); 143 | 144 | this._carouselCssNode = this._createStyleElem(); 145 | 146 | if (this._isBrowser) { 147 | this._carouselInterval(); 148 | if (!this.vertical.enabled && this.inputs()?.touch) { 149 | this._setupHammer(); 150 | } 151 | this._setupWindowResizeListener(); 152 | this._onWindowScrolling(); 153 | } 154 | }, 155 | { phase: AfterRenderPhase.EarlyRead } 156 | ); 157 | 158 | effect(() => { 159 | const _ = this._defDirectives(); 160 | const data = this.dataSource(); 161 | untracked(() => this._checkChanges(data)); 162 | }); 163 | 164 | effect(cleanup => { 165 | const prevButton = this.prevButton(); 166 | untracked(() => { 167 | if (prevButton) { 168 | const preSub = fromEvent(prevButton.nativeElement, 'click') 169 | .pipe(takeUntil(this._destroy$)) 170 | .subscribe(() => this._carouselScrollOne(0)); 171 | cleanup(() => preSub.unsubscribe()); 172 | } 173 | }); 174 | }); 175 | 176 | effect(cleanup => { 177 | const nextButton = this.nextButton(); 178 | untracked(() => { 179 | if (nextButton) { 180 | const nextSub = fromEvent(nextButton.nativeElement, 'click') 181 | .pipe(takeUntil(this._destroy$)) 182 | .subscribe(() => this._carouselScrollOne(1)); 183 | cleanup(() => nextSub.unsubscribe()); 184 | } 185 | }); 186 | }); 187 | } 188 | 189 | private _checkChanges(data: NguCarouselDataSource) { 190 | // if (this.ngu_dirty) { 191 | // this.ngu_dirty = false; 192 | // const dataStream = this.dataSource; 193 | // if (!this._arrayChanges && !!dataStream) { 194 | // this._dataDiffer = this._differs 195 | // .find(dataStream) 196 | // .create((index: number, item: any) => this._trackByFn()(index, item))!; 197 | // } 198 | // } 199 | // if (this._dataDiffer) { 200 | // this._arrayChanges = 201 | // this._markedForCheck && this._arrayChanges 202 | // ? this._arrayChanges 203 | // : this._dataDiffer.diff(this.dataSource)!; 204 | // if (this._arrayChanges) { 205 | // this.renderNodeChanges(Array.from(this.dataSource())); 206 | // } 207 | // } 208 | this._arrayChanges = this._dataDiffer.diff(data); 209 | if (this._arrayChanges) { 210 | this.renderNodeChanges(Array.from(data as any)); 211 | } 212 | } 213 | 214 | private renderNodeChanges(data: any[]) { 215 | if (!this._arrayChanges) return; 216 | this.isLast.set(this._pointIndex === this.currentSlide); 217 | const viewContainer = this._nodeOutlet().viewContainer; 218 | // this._markedForCheck = false; 219 | this._arrayChanges.forEachOperation( 220 | ( 221 | item: IterableChangeRecord, 222 | adjustedPreviousIndex: number | null, 223 | currentIndex: number | null 224 | ) => { 225 | const node = this._getNodeDef(data[currentIndex!], currentIndex!); 226 | if (node?.template) { 227 | if (item.previousIndex == null) { 228 | const context = new NguCarouselOutletContext(data[currentIndex!]); 229 | context.index = currentIndex!; 230 | viewContainer.createEmbeddedView(node.template, context, currentIndex!); 231 | } else if (currentIndex == null) { 232 | viewContainer.remove(adjustedPreviousIndex!); 233 | } else { 234 | const view = viewContainer.get(adjustedPreviousIndex!); 235 | viewContainer.move(view!, currentIndex); 236 | } 237 | } 238 | } 239 | ); 240 | this._updateItemIndexContext(); 241 | 242 | if (this._host.nativeElement) { 243 | this._storeCarouselData(); 244 | } 245 | } 246 | 247 | /** 248 | * Updates the index-related context for each row to reflect any changes in the index of the rows, 249 | * e.g. first/last/even/odd. 250 | */ 251 | private _updateItemIndexContext() { 252 | const viewContainer = this._nodeOutlet().viewContainer; 253 | for (let renderIndex = 0, count = viewContainer.length; renderIndex < count; renderIndex++) { 254 | const viewRef = viewContainer.get(renderIndex) as any; 255 | const context = viewRef.context as any; 256 | context.count = count; 257 | context.first = renderIndex === 0; 258 | context.last = renderIndex === count - 1; 259 | context.even = renderIndex % 2 === 0; 260 | context.odd = !context.even; 261 | context.index = renderIndex; 262 | } 263 | } 264 | 265 | private _getNodeDef(data: any, i: number): NguCarouselDefDirective | undefined { 266 | if (this._defDirectives()?.length === 1) { 267 | return this._defDirectives()[0]; 268 | } 269 | 270 | const nodeDef: NguCarouselDefDirective | undefined = (this._defDirectives() || []).find( 271 | def => !!def.when?.(i, data) 272 | ); 273 | 274 | return nodeDef; 275 | } 276 | 277 | private _inputValidation() { 278 | const inputs = this.inputs(); 279 | inputs.gridBreakpoints = inputs.gridBreakpoints ? inputs.gridBreakpoints : new Breakpoints(); 280 | if (inputs.grid.xl === undefined) { 281 | inputs.grid.xl = inputs.grid.lg; 282 | } 283 | 284 | this.type = inputs.grid.all !== 0 ? 'fixed' : 'responsive'; 285 | this.loop = inputs.loop || false; 286 | inputs.easing = inputs.easing || 'cubic-bezier(0, 0, 0.2, 1)'; 287 | this.touch.active = inputs.touch || false; 288 | this.RTL = inputs.RTL ? true : false; 289 | this.interval = inputs.interval || undefined; 290 | this.velocity = typeof inputs.velocity === 'number' ? inputs.velocity : this.velocity; 291 | 292 | if (inputs.vertical && inputs.vertical.enabled) { 293 | this.vertical.enabled = inputs.vertical.enabled; 294 | this.vertical.height = inputs.vertical.height; 295 | } 296 | this._directionSymbol = this.RTL ? '' : '-'; 297 | this.point = 298 | inputs.point && typeof inputs.point.visible !== 'undefined' ? inputs.point.visible : true; 299 | 300 | this._carouselSize(); 301 | } 302 | 303 | ngOnDestroy() { 304 | this._hammer?.destroy(); 305 | this._destroy$.next(); 306 | } 307 | 308 | /** Get Touch input */ 309 | private _setupHammer(): void { 310 | // Note: doesn't need to unsubscribe because streams are piped with `takeUntil` already. 311 | this._nguCarouselHammerManager 312 | .createHammer(this._touchContainer().nativeElement) 313 | .subscribe(hammer => { 314 | this._hammer = hammer; 315 | 316 | hammer.get('pan').set({ direction: Hammer.DIRECTION_HORIZONTAL }); 317 | 318 | this._nguCarouselHammerManager.on(hammer, 'panstart').subscribe(() => { 319 | this.carouselWidth = this._nguItemsContainer().nativeElement.offsetWidth; 320 | this.touchTransform = this.transform[this.deviceType!]!; 321 | this.dexVal = 0; 322 | this._setStyle(this._nguItemsContainer().nativeElement, 'transition', ''); 323 | }); 324 | 325 | if (this.vertical.enabled) { 326 | this._nguCarouselHammerManager.on(hammer, 'panup').subscribe((ev: any) => { 327 | this._touchHandling('panleft', ev); 328 | }); 329 | 330 | this._nguCarouselHammerManager.on(hammer, 'pandown').subscribe((ev: any) => { 331 | this._touchHandling('panright', ev); 332 | }); 333 | } else { 334 | this._nguCarouselHammerManager.on(hammer, 'panleft').subscribe((ev: any) => { 335 | this._touchHandling('panleft', ev); 336 | }); 337 | 338 | this._nguCarouselHammerManager.on(hammer, 'panright').subscribe((ev: any) => { 339 | this._touchHandling('panright', ev); 340 | }); 341 | } 342 | 343 | this._nguCarouselHammerManager.on(hammer, 'panend pancancel').subscribe(({ velocity }) => { 344 | if (Math.abs(velocity) >= this.velocity) { 345 | this.touch.velocity = velocity; 346 | let direc = 0; 347 | if (!this.RTL) { 348 | direc = this.touch.swipe === 'panright' ? 0 : 1; 349 | } else { 350 | direc = this.touch.swipe === 'panright' ? 1 : 0; 351 | } 352 | this._carouselScrollOne(direc); 353 | } else { 354 | this.dexVal = 0; 355 | this._setStyle( 356 | this._nguItemsContainer().nativeElement, 357 | 'transition', 358 | 'transform 324ms cubic-bezier(0, 0, 0.2, 1)' 359 | ); 360 | this._setStyle(this._nguItemsContainer().nativeElement, 'transform', ''); 361 | } 362 | }); 363 | 364 | this._nguCarouselHammerManager.on(hammer, 'hammer.input').subscribe(({ srcEvent }) => { 365 | // allow nested touch events to no propagate, this may have other side affects but works for now. 366 | // TODO: It is probably better to check the source element of the event and only apply the handle to the correct carousel 367 | srcEvent.stopPropagation(); 368 | }); 369 | }); 370 | } 371 | 372 | /** handle touch input */ 373 | private _touchHandling(e: string, ev: any): void { 374 | // vertical touch events seem to cause to panstart event with an odd delta 375 | // and a center of {x:0,y:0} so this will ignore them 376 | if (ev.center.x === 0) { 377 | return; 378 | } 379 | 380 | ev = Math.abs(this.vertical.enabled ? ev.deltaY : ev.deltaX); 381 | let valt = ev - this.dexVal; 382 | valt = 383 | this.type === 'responsive' 384 | ? (Math.abs(ev - this.dexVal) / 385 | (this.vertical.enabled ? this.vertical.height : this.carouselWidth)) * 386 | 100 387 | : valt; 388 | this.dexVal = ev; 389 | this.touch.swipe = e; 390 | this._setTouchTransfrom(e, valt); 391 | this._setTransformFromTouch(); 392 | } 393 | 394 | private _setTouchTransfrom(e: string, valt: number) { 395 | const condition = this.RTL ? 'panright' : 'panleft'; 396 | this.touchTransform = e === condition ? valt + this.touchTransform : this.touchTransform - valt; 397 | } 398 | 399 | private _setTransformFromTouch() { 400 | if (this.touchTransform < 0) { 401 | this.touchTransform = 0; 402 | } 403 | const type = this.type === 'responsive' ? '%' : 'px'; 404 | this._setStyle( 405 | this._nguItemsContainer().nativeElement, 406 | 'transform', 407 | this.vertical.enabled 408 | ? `translate3d(0, ${this._directionSymbol}${this.touchTransform}${type}, 0)` 409 | : `translate3d(${this._directionSymbol}${this.touchTransform}${type}, 0, 0)` 410 | ); 411 | } 412 | 413 | /** this fn used to disable the interval when it is not on the viewport */ 414 | private _onWindowScrolling(): void { 415 | const { offsetTop, offsetHeight } = this._host.nativeElement; 416 | const { scrollY: windowScrollY, innerHeight: windowInnerHeight } = window; 417 | 418 | const isCarouselOnScreen = 419 | offsetTop <= windowScrollY + windowInnerHeight - offsetHeight / 4 && 420 | offsetHeight + offsetHeight / 2 >= windowScrollY; 421 | 422 | if (isCarouselOnScreen) { 423 | this._intervalController$.next(1); 424 | } else { 425 | this._intervalController$.next(0); 426 | } 427 | } 428 | 429 | /** store data based on width of the screen for the carousel */ 430 | private _storeCarouselData(): void { 431 | const inputs = this.inputs(); 432 | const breakpoints = this.inputs().gridBreakpoints; 433 | this.deviceWidth = this._isBrowser ? window.innerWidth : breakpoints?.xl!; 434 | 435 | this.carouselWidth = this.carouselMain1().nativeElement.offsetWidth; 436 | 437 | if (this.type === 'responsive') { 438 | this.deviceType = 439 | this.deviceWidth >= breakpoints?.xl! 440 | ? 'xl' 441 | : this.deviceWidth >= breakpoints?.lg! 442 | ? 'lg' 443 | : this.deviceWidth >= breakpoints?.md! 444 | ? 'md' 445 | : this.deviceWidth >= breakpoints?.sm! 446 | ? 'sm' 447 | : 'xs'; 448 | 449 | this.items = inputs.grid[this.deviceType]!; 450 | this.itemWidth = this.carouselWidth / this.items; 451 | } else { 452 | this.items = Math.trunc(this.carouselWidth / inputs.grid.all); 453 | this.itemWidth = inputs.grid.all; 454 | this.deviceType = 'all'; 455 | } 456 | 457 | this.slideItems = +(inputs.slide! < this.items ? inputs.slide! : this.items); 458 | this.load = inputs.load! >= this.slideItems ? inputs.load! : this.slideItems; 459 | this.speed = inputs.speed && inputs.speed > -1 ? inputs.speed : 400; 460 | this._carouselPoint(); 461 | } 462 | 463 | /** Used to reset the carousel */ 464 | public reset(withoutAnimation?: boolean): void { 465 | withoutAnimation && (this._withAnimation = false); 466 | this._carouselCssNode.textContent = ''; 467 | this.moveTo(0); 468 | this._carouselPoint(); 469 | } 470 | 471 | /** Init carousel point */ 472 | private _carouselPoint(): void { 473 | const Nos = Array.from(this.dataSource()).length - (this.items - this.slideItems); 474 | this._pointIndex = Math.ceil(Nos / this.slideItems); 475 | const pointers: number[] = []; 476 | 477 | if (this._pointIndex > 1 || !this.inputs().point?.hideOnSingleSlide) { 478 | for (let i = 0; i < this._pointIndex; i++) { 479 | pointers.push(i); 480 | } 481 | } 482 | this.pointNumbers.set(pointers); 483 | 484 | this._carouselPointActiver(); 485 | if (this._pointIndex <= 1) { 486 | this._btnBoolean(1, 1); 487 | } else { 488 | if (this.currentSlide === 0 && !this.loop) { 489 | this._btnBoolean(1, 0); 490 | } else { 491 | this._btnBoolean(0, 0); 492 | } 493 | } 494 | } 495 | 496 | /** change the active point in carousel */ 497 | private _carouselPointActiver(): void { 498 | const i = Math.ceil(this.currentSlide / this.slideItems); 499 | this.activePoint.set(i); 500 | } 501 | 502 | /** this function is used to scoll the carousel when point is clicked */ 503 | public moveTo(slide: number, withoutAnimation?: boolean) { 504 | // slide = slide - 1; 505 | withoutAnimation && (this._withAnimation = false); 506 | if (this.activePoint() !== slide && slide < this._pointIndex) { 507 | let slideremains; 508 | const btns = this.currentSlide < slide ? 1 : 0; 509 | 510 | switch (slide) { 511 | case 0: 512 | this._btnBoolean(1, 0); 513 | slideremains = slide * this.slideItems; 514 | break; 515 | case this._pointIndex - 1: 516 | this._btnBoolean(0, 1); 517 | slideremains = Array.from(this.dataSource()).length - this.items; 518 | break; 519 | default: 520 | this._btnBoolean(0, 0); 521 | slideremains = slide * this.slideItems; 522 | } 523 | this._carouselScrollTwo(btns, slideremains, this.speed); 524 | } 525 | } 526 | 527 | /** set the style of the carousel based the inputs data */ 528 | private _carouselSize(): void { 529 | const inputs = this.inputs(); 530 | this.token = this._generateID(); 531 | let dism = ''; 532 | this._styleid = `.${this.token} > .ngucarousel > .ngu-container > .ngu-touch-container > .ngucarousel-items`; 533 | 534 | if (inputs.custom === 'banner') { 535 | this._renderer.addClass(this._host.nativeElement, 'banner'); 536 | } 537 | 538 | if (inputs.animation === 'lazy') { 539 | dism += `${this._styleid} > .item {transition: transform .6s ease;}`; 540 | } 541 | 542 | const breakpoints = inputs.gridBreakpoints; 543 | 544 | let itemStyle = ''; 545 | if (this.vertical.enabled) { 546 | const itemWidthXS = `${this._styleid} > .item {height: ${ 547 | this.vertical.height / +inputs.grid.xs 548 | }px}`; 549 | const itemWidthSM = `${this._styleid} > .item {height: ${ 550 | this.vertical.height / +inputs.grid.sm 551 | }px}`; 552 | const itemWidthMD = `${this._styleid} > .item {height: ${ 553 | this.vertical.height / +inputs.grid.md 554 | }px}`; 555 | const itemWidthLG = `${this._styleid} > .item {height: ${ 556 | this.vertical.height / +inputs.grid.lg 557 | }px}`; 558 | const itemWidthXL = `${this._styleid} > .item {height: ${ 559 | this.vertical.height / +inputs.grid.xl! 560 | }px}`; 561 | 562 | itemStyle = `@media (max-width:${breakpoints?.sm! - 1}px){${itemWidthXS}} 563 | @media (max-width:${breakpoints?.sm}px){${itemWidthSM}} 564 | @media (min-width:${breakpoints?.md}px){${itemWidthMD}} 565 | @media (min-width:${breakpoints?.lg}px){${itemWidthLG}} 566 | @media (min-width:${breakpoints?.xl}px){${itemWidthXL}}`; 567 | } else if (this.type === 'responsive') { 568 | const itemWidthXS = 569 | inputs.type === 'mobile' 570 | ? `${this._styleid} .item {flex: 0 0 ${95 / +inputs.grid.xs}%; width: ${ 571 | 95 / +inputs.grid.xs 572 | }%;}` 573 | : `${this._styleid} .item {flex: 0 0 ${100 / +inputs.grid.xs}%; width: ${ 574 | 100 / +inputs.grid.xs 575 | }%;}`; 576 | 577 | const itemWidthSM = `${this._styleid} > .item {flex: 0 0 ${100 / +inputs.grid.sm}%; width: ${ 578 | 100 / +inputs.grid.sm 579 | }%}`; 580 | const itemWidthMD = `${this._styleid} > .item {flex: 0 0 ${100 / +inputs.grid.md}%; width: ${ 581 | 100 / +inputs.grid.md 582 | }%}`; 583 | const itemWidthLG = `${this._styleid} > .item {flex: 0 0 ${100 / +inputs.grid.lg}%; width: ${ 584 | 100 / +inputs.grid.lg 585 | }%}`; 586 | const itemWidthXL = `${this._styleid} > .item {flex: 0 0 ${100 / +inputs.grid.xl!}%; width: ${ 587 | 100 / +inputs.grid.xl! 588 | }%}`; 589 | 590 | itemStyle = `@media (max-width:${breakpoints?.sm! - 1}px){${itemWidthXS}} 591 | @media (min-width:${breakpoints?.sm}px){${itemWidthSM}} 592 | @media (min-width:${breakpoints?.md}px){${itemWidthMD}} 593 | @media (min-width:${breakpoints?.lg}px){${itemWidthLG}} 594 | @media (min-width:${breakpoints?.xl}px){${itemWidthXL}}`; 595 | } else { 596 | itemStyle = `${this._styleid} .item {flex: 0 0 ${inputs.grid.all}px; width: ${inputs.grid.all}px;}`; 597 | } 598 | 599 | this._renderer.addClass(this._host.nativeElement, this.token); 600 | if (this.vertical.enabled) { 601 | this._renderer.addClass(this._nguItemsContainer().nativeElement, 'nguvertical'); 602 | this._renderer.setStyle( 603 | this.carouselMain1().nativeElement, 604 | 'height', 605 | `${this.vertical.height}px` 606 | ); 607 | } 608 | 609 | this.RTL && 610 | !this.vertical.enabled && 611 | this._renderer.addClass(this._host.nativeElement, 'ngurtl'); 612 | this._createStyleElem(`${dism} ${itemStyle}`); 613 | this._storeCarouselData(); 614 | } 615 | 616 | /** logic to scroll the carousel step 1 */ 617 | private _carouselScrollOne(Btn: number): void { 618 | let itemSpeed = this.speed; 619 | let currentSlide = 0; 620 | let touchMove = Math.ceil(this.dexVal / this.itemWidth); 621 | touchMove = isFinite(touchMove) ? touchMove : 0; 622 | this._setStyle(this._nguItemsContainer().nativeElement, 'transform', ''); 623 | 624 | if (this._pointIndex === 1) { 625 | return; 626 | } else if (Btn === 0 && ((!this.loop && !this.isFirst()) || this.loop)) { 627 | const currentSlideD = this.currentSlide - this.slideItems; 628 | const MoveSlide = currentSlideD + this.slideItems; 629 | this._btnBoolean(0, 1); 630 | if (this.currentSlide === 0) { 631 | currentSlide = Array.from(this.dataSource()).length - this.items; 632 | itemSpeed = 400; 633 | this._btnBoolean(0, 1); 634 | } else if (this.slideItems >= MoveSlide) { 635 | currentSlide = 0; 636 | this._btnBoolean(1, 0); 637 | } else { 638 | this._btnBoolean(0, 0); 639 | if (touchMove > this.slideItems) { 640 | currentSlide = this.currentSlide - touchMove; 641 | itemSpeed = 200; 642 | } else { 643 | currentSlide = this.currentSlide - this.slideItems; 644 | } 645 | } 646 | this._carouselScrollTwo(Btn, currentSlide, itemSpeed); 647 | } else if (Btn === 1 && ((!this.loop && !this.isLast()) || this.loop)) { 648 | if ( 649 | Array.from(this.dataSource()).length <= this.currentSlide + this.items + this.slideItems && 650 | !this.isLast() 651 | ) { 652 | currentSlide = Array.from(this.dataSource()).length - this.items; 653 | this._btnBoolean(0, 1); 654 | } else if (this.isLast()) { 655 | currentSlide = 0; 656 | itemSpeed = 400; 657 | this._btnBoolean(1, 0); 658 | } else { 659 | this._btnBoolean(0, 0); 660 | if (touchMove > this.slideItems) { 661 | currentSlide = this.currentSlide + this.slideItems + (touchMove - this.slideItems); 662 | itemSpeed = 200; 663 | } else { 664 | currentSlide = this.currentSlide + this.slideItems; 665 | } 666 | } 667 | this._carouselScrollTwo(Btn, currentSlide, itemSpeed); 668 | } 669 | } 670 | 671 | /** logic to scroll the carousel step 2 */ 672 | private _carouselScrollTwo(Btn: number, currentSlide: number, itemSpeed: number): void { 673 | if (this.dexVal !== 0) { 674 | const val = Math.abs(this.touch.velocity); 675 | let somt = Math.floor((this.dexVal / val / this.dexVal) * (this.deviceWidth - this.dexVal)); 676 | somt = somt > itemSpeed ? itemSpeed : somt; 677 | itemSpeed = somt < 200 ? 200 : somt; 678 | this.dexVal = 0; 679 | } 680 | if (this._withAnimation) { 681 | this._setStyle( 682 | this._nguItemsContainer().nativeElement, 683 | 'transition', 684 | `transform ${itemSpeed}ms ${this.inputs().easing}` 685 | ); 686 | this.inputs().animation && 687 | this._carouselAnimator( 688 | Btn, 689 | currentSlide + 1, 690 | currentSlide + this.items, 691 | itemSpeed, 692 | Math.abs(this.currentSlide - currentSlide) 693 | ); 694 | } else { 695 | this._setStyle(this._nguItemsContainer().nativeElement, 'transition', ``); 696 | } 697 | 698 | this.itemLength = Array.from(this.dataSource()).length; 699 | this._transformStyle(currentSlide); 700 | this.currentSlide = currentSlide; 701 | this.onMove.emit(this); 702 | this._carouselPointActiver(); 703 | this._carouselLoadTrigger(); 704 | this._withAnimation = true; 705 | } 706 | 707 | /** boolean function for making isFirst and isLast */ 708 | private _btnBoolean(first: number, last: number) { 709 | this.isFirst.set(!!first); 710 | this.isLast.set(!!last); 711 | } 712 | 713 | private _transformString(grid: keyof Transfrom, slide: number): string { 714 | let collect = ''; 715 | collect += `${this._styleid} { transform: translate3d(`; 716 | 717 | if (this.vertical.enabled) { 718 | this.transform[grid] = (this.vertical.height / this.inputs().grid[grid]!) * slide; 719 | collect += `0, -${this.transform[grid]}px, 0`; 720 | } else { 721 | this.transform[grid] = (100 / this.inputs().grid[grid]!) * slide; 722 | collect += `${this._directionSymbol}${this.transform[grid]}%, 0, 0`; 723 | } 724 | collect += `); }`; 725 | return collect; 726 | } 727 | 728 | /** set the transform style to scroll the carousel */ 729 | private _transformStyle(slide: number): void { 730 | let slideCss = ''; 731 | if (this.type === 'responsive') { 732 | const breakpoints = this.inputs().gridBreakpoints; 733 | slideCss = `@media (max-width: ${breakpoints?.sm! - 1}px) {${this._transformString( 734 | 'xs', 735 | slide 736 | )}} 737 | @media (min-width: ${breakpoints?.sm}px) {${this._transformString('sm', slide)} } 738 | @media (min-width: ${breakpoints?.md}px) {${this._transformString('md', slide)} } 739 | @media (min-width: ${breakpoints?.lg}px) {${this._transformString('lg', slide)} } 740 | @media (min-width: ${breakpoints?.xl}px) {${this._transformString('xl', slide)} }`; 741 | } else { 742 | this.transform.all = this.inputs().grid.all * slide; 743 | slideCss = `${this._styleid} { transform: translate3d(${this._directionSymbol}${this.transform.all}px, 0, 0);`; 744 | } 745 | this._carouselCssNode.textContent = slideCss; 746 | } 747 | 748 | /** this will trigger the carousel to load the items */ 749 | private _carouselLoadTrigger(): void { 750 | if (typeof this.inputs().load === 'number') { 751 | Array.from(this.dataSource()).length - this.load <= this.currentSlide + this.items && 752 | this.carouselLoad.emit(this.currentSlide); 753 | } 754 | } 755 | 756 | /** generate Class for each carousel to set specific style */ 757 | private _generateID(): string { 758 | let text = ''; 759 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 760 | 761 | for (let i = 0; i < 6; i++) { 762 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 763 | } 764 | return `ngucarousel${text}`; 765 | } 766 | 767 | /** handle the auto slide */ 768 | private _carouselInterval(): void { 769 | const container = this.carouselMain1().nativeElement; 770 | if (this.interval && this.loop) { 771 | this._nguWindowScrollListener 772 | .pipe( 773 | // Note: do not use `debounceTime` since it may flush queued actions within the Angular zone. 774 | switchMap(() => timer(600)), 775 | takeUntil(this._destroy$) 776 | ) 777 | .subscribe(() => { 778 | // Note: we don't run change detection on each `scroll` event, but we re-enter the 779 | // Angular zone once the DOM timer fires to be backwards compatible. 780 | // TODO: revisit later since we may not run change detection at all on this task. 781 | this._ngZone.run(() => this._onWindowScrolling()); 782 | }); 783 | 784 | const mapToZero = map(() => 0); 785 | const mapToOne = map(() => 1); 786 | 787 | const play$ = fromEvent(container, 'mouseleave').pipe(mapToOne); 788 | const pause$ = fromEvent(container, 'mouseenter').pipe(mapToZero); 789 | 790 | const touchPlay$ = fromEvent(container, 'touchstart').pipe(mapToOne); 791 | const touchPause$ = fromEvent(container, 'touchend').pipe(mapToZero); 792 | 793 | const interval$ = interval(this.inputs().interval?.timing!).pipe(mapToOne); 794 | 795 | const initialDelay = this.interval.initialDelay || 0; 796 | 797 | const carouselInterval$ = merge( 798 | play$, 799 | touchPlay$, 800 | pause$, 801 | touchPause$, 802 | this._intervalController$ 803 | ).pipe( 804 | startWith(1), 805 | switchMap(val => { 806 | this._cdr.markForCheck(); 807 | return val ? interval$ : EMPTY; 808 | }) 809 | ); 810 | 811 | timer(initialDelay) 812 | .pipe( 813 | switchMap(() => carouselInterval$), 814 | takeUntil(this._destroy$) 815 | ) 816 | .subscribe(() => { 817 | this._carouselScrollOne(1); 818 | }); 819 | } 820 | } 821 | 822 | /** animate the carousel items */ 823 | private _carouselAnimator( 824 | direction: number, 825 | start: number, 826 | end: number, 827 | speed: number, 828 | length: number 829 | ): void { 830 | const viewContainer = this._nodeOutlet().viewContainer; 831 | 832 | let val = length < 5 ? length : 5; 833 | val = val === 1 ? 3 : val; 834 | const collectedIndexes: number[] = []; 835 | 836 | if (direction === 1) { 837 | for (let i = start - 1; i < end; i++) { 838 | collectedIndexes.push(i); 839 | val = val * 2; 840 | const viewRef = viewContainer.get(i) as any; 841 | const context = viewRef.context as any; 842 | context.animate = { value: true, params: { distance: val } }; 843 | } 844 | } else { 845 | for (let i = end - 1; i >= start - 1; i--) { 846 | collectedIndexes.push(i); 847 | val = val * 2; 848 | const viewRef = viewContainer.get(i) as any; 849 | const context = viewRef.context as any; 850 | context.animate = { value: true, params: { distance: -val } }; 851 | } 852 | } 853 | 854 | timer(speed * 0.7) 855 | .pipe(takeUntil(this._destroy$)) 856 | .subscribe(() => this._removeAnimations(collectedIndexes)); 857 | } 858 | 859 | private _removeAnimations(collectedIndexes: number[]) { 860 | const viewContainer = this._nodeOutlet().viewContainer; 861 | collectedIndexes.forEach(i => { 862 | const viewRef = viewContainer.get(i) as any; 863 | const context = viewRef.context as any; 864 | context.animate = { value: false, params: { distance: 0 } }; 865 | }); 866 | this._cdr.markForCheck(); 867 | } 868 | 869 | /** Short form for setElementStyle */ 870 | private _setStyle(el: any, prop: any, val: any): void { 871 | this._renderer.setStyle(el, prop, val); 872 | } 873 | 874 | /** For generating style tag */ 875 | private _createStyleElem(datas?: string): HTMLStyleElement { 876 | const styleItem = this._renderer.createElement('style'); 877 | if (datas) { 878 | const styleText = this._renderer.createText(datas); 879 | this._renderer.appendChild(styleItem, styleText); 880 | } 881 | this._renderer.appendChild(this._host.nativeElement, styleItem); 882 | return styleItem; 883 | } 884 | 885 | private _setupWindowResizeListener(): void { 886 | this._ngZone.runOutsideAngular(() => 887 | fromEvent(window, 'resize') 888 | .pipe( 889 | debounceTime(500), 890 | filter(() => this.deviceWidth !== window.outerWidth), 891 | takeUntil(this._destroy$) 892 | ) 893 | .subscribe(() => { 894 | this._setStyle(this._nguItemsContainer().nativeElement, 'transition', ``); 895 | // Re-enter the Angular zone only after `resize` events have been dispatched 896 | // and the timer has run (in `debounceTime`). 897 | this._ngZone.run(() => { 898 | this._storeCarouselData(); 899 | // this._cdr.markForCheck(); 900 | }); 901 | }) 902 | ); 903 | } 904 | 905 | static ngAcceptInputType_dataSource: NguCarouselDataSource; 906 | } 907 | -------------------------------------------------------------------------------- /libs/ngu/carousel/src/lib/ngu-carousel/ngu-carousel.ts: -------------------------------------------------------------------------------- 1 | import { signal } from '@angular/core'; 2 | 3 | export class NguCarouselStore { 4 | constructor( 5 | public touch = new Touch(), 6 | public vertical = new Vertical(), 7 | public interval?: CarouselInterval, 8 | public transform = new Transfrom(), 9 | public button?: NguButton, 10 | public visibleItems?: ItemsControl, 11 | public deviceType?: DeviceType, 12 | public type = 'fixed', 13 | public token = '', 14 | public items = 0, 15 | public load = 0, 16 | public deviceWidth = 0, 17 | public carouselWidth = 0, 18 | public itemWidth = 0, 19 | public slideItems = 0, 20 | public itemWidthPer = 0, 21 | public itemLength = 0, 22 | public currentSlide = 0, 23 | public easing = 'cubic-bezier(0, 0, 0.2, 1)', 24 | public speed = 200, 25 | public loop = false, 26 | public dexVal = 0, 27 | public touchTransform = 0, 28 | public isEnd = false, 29 | public readonly isFirst = signal(true), 30 | public readonly isLast = signal(false), 31 | public RTL = false, 32 | public point = true, 33 | public velocity = 1 34 | ) {} 35 | } 36 | export type DeviceType = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'all'; 37 | 38 | export type ButtonVisible = 'disabled' | 'hide'; 39 | 40 | export class ItemsControl { 41 | start: number; 42 | end: number; 43 | } 44 | 45 | export class Vertical { 46 | enabled: boolean; 47 | height: number; 48 | // numHeight?: number; 49 | } 50 | 51 | export class NguButton { 52 | visibility?: ButtonVisible; 53 | elastic?: number; 54 | } 55 | 56 | export class Touch { 57 | active?: boolean; 58 | swipe: string; 59 | velocity: number; 60 | } 61 | 62 | // Interface is declared to prevent property-minification 63 | // See: https://github.com/uiuniversal/ngu-carousel/issues/322 64 | declare interface TransformInterface { 65 | xs: number; 66 | sm: number; 67 | md: number; 68 | lg: number; 69 | xl?: number; 70 | all: number; 71 | } 72 | 73 | // This is misspelled. Must be changed to `Transform`. 74 | export class Transfrom implements TransformInterface { 75 | public xl? = 0; 76 | constructor( 77 | public xs = 0, 78 | public sm = 0, 79 | public md = 0, 80 | public lg = 0, 81 | public all = 0 82 | ) {} 83 | } 84 | 85 | // Interface is declared to prevent property-minification 86 | // See: https://github.com/uiuniversal/ngu-carousel/issues/322 87 | declare interface BreakpointsInterface { 88 | sm: number; 89 | md: number; 90 | lg: number; 91 | xl: number; 92 | } 93 | 94 | /** 95 | * Default values 96 | * {sm: 768, md: 992, lg: 1200, xl: 1200} 97 | * 98 | * Bootstrap values: 99 | * {sm: 576, md: 768, lg: 992, xl: 1200} 100 | */ 101 | export class Breakpoints implements BreakpointsInterface { 102 | constructor( 103 | public sm = 768, 104 | public md = 992, 105 | public lg = 1200, 106 | public xl = 1200 107 | ) {} 108 | } 109 | 110 | export class NguCarouselConfig { 111 | grid: Transfrom; 112 | gridBreakpoints?: Breakpoints; 113 | slide?: number; 114 | speed?: number; 115 | interval?: CarouselInterval; 116 | animation?: Animate; 117 | point?: Point; 118 | type?: string; 119 | load?: number; 120 | custom?: Custom; 121 | loop?: boolean; 122 | touch?: boolean; 123 | easing?: string; 124 | RTL?: boolean; 125 | button?: NguButton; 126 | vertical?: Vertical; 127 | velocity?: number; 128 | } 129 | 130 | export type Custom = 'banner'; 131 | export type Animate = 'lazy'; 132 | 133 | export interface Point { 134 | visible: boolean; 135 | hideOnSingleSlide?: boolean; 136 | } 137 | 138 | export interface Animation { 139 | type?: Animate; 140 | animateStyles?: AnimationStyles; 141 | } 142 | 143 | export interface AnimationStyles { 144 | style?: string; 145 | open?: string; 146 | close?: string; 147 | stagger?: number; 148 | } 149 | 150 | export interface CarouselInterval { 151 | timing: number; 152 | initialDelay?: number; 153 | } 154 | 155 | export class NguCarouselOutletContext { 156 | /** Data for the node. */ 157 | $implicit: T; 158 | 159 | /** Depth of the node. */ 160 | level: number; 161 | 162 | /** Index location of the node. */ 163 | index?: number; 164 | 165 | /** Length of the number of total dataNodes. */ 166 | count?: number; 167 | 168 | constructor(data: T) { 169 | this.$implicit = data; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /libs/ngu/carousel/src/lib/ngu-carousel/ngu-window-scroll-listener.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NgZone, OnDestroy, inject } from '@angular/core'; 2 | import { Subject, fromEvent } from 'rxjs'; 3 | import { takeUntil } from 'rxjs/operators'; 4 | 5 | import { IS_BROWSER } from '../symbols'; 6 | 7 | @Injectable({ providedIn: 'root' }) 8 | export class NguWindowScrollListener extends Subject implements OnDestroy { 9 | private readonly _destroy$ = new Subject(); 10 | 11 | constructor() { 12 | super(); 13 | 14 | const isBrowser = inject(IS_BROWSER); 15 | const ngZone = inject(NgZone); 16 | // Note: this service is shared between multiple `NguCarousel` components and each instance 17 | // doesn't add new events listener for the `window`. 18 | if (isBrowser) { 19 | ngZone.runOutsideAngular(() => 20 | fromEvent(window, 'scroll').pipe(takeUntil(this._destroy$)).subscribe(this) 21 | ); 22 | } 23 | } 24 | 25 | ngOnDestroy(): void { 26 | this._destroy$.next(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /libs/ngu/carousel/src/lib/ngu-item/ngu-item.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /libs/ngu/carousel/src/lib/ngu-item/ngu-item.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uiuniversal/ngu-carousel/735c71291bd3af93980adb6aafc6b701abb808d3/libs/ngu/carousel/src/lib/ngu-item/ngu-item.component.scss -------------------------------------------------------------------------------- /libs/ngu/carousel/src/lib/ngu-item/ngu-item.component.stories.ts: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/angular'; 2 | import { NguItemComponent } from './ngu-item.component'; 3 | 4 | export default { 5 | title: 'NguItemComponent', 6 | component: NguItemComponent 7 | } as Meta; 8 | 9 | export const Primary = { 10 | render: (args: NguItemComponent) => ({ 11 | props: args 12 | }), 13 | args: {} 14 | }; 15 | -------------------------------------------------------------------------------- /libs/ngu/carousel/src/lib/ngu-item/ngu-item.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'ngu-item', 5 | templateUrl: 'ngu-item.component.html', 6 | host: { 7 | class: 'item' 8 | } 9 | }) 10 | export class NguItemComponent {} 11 | -------------------------------------------------------------------------------- /libs/ngu/carousel/src/lib/ngu-tile/ngu-tile.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /libs/ngu/carousel/src/lib/ngu-tile/ngu-tile.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | padding: 10px; 3 | box-sizing: border-box; 4 | } 5 | 6 | .tile { 7 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12); 8 | } 9 | -------------------------------------------------------------------------------- /libs/ngu/carousel/src/lib/ngu-tile/ngu-tile.component.stories.ts: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/angular'; 2 | import { NguTileComponent } from './ngu-tile.component'; 3 | 4 | export default { 5 | title: 'NguTileComponent', 6 | component: NguTileComponent 7 | } as Meta; 8 | 9 | export const Primary = { 10 | render: (args: NguTileComponent) => ({ 11 | props: args 12 | }), 13 | args: {} 14 | }; 15 | -------------------------------------------------------------------------------- /libs/ngu/carousel/src/lib/ngu-tile/ngu-tile.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'ngu-tile', 5 | templateUrl: 'ngu-tile.component.html', 6 | styleUrls: ['ngu-tile.component.scss'], 7 | host: { 8 | class: 'item' 9 | } 10 | }) 11 | export class NguTileComponent {} 12 | -------------------------------------------------------------------------------- /libs/ngu/carousel/src/lib/symbols.ts: -------------------------------------------------------------------------------- 1 | import { isPlatformBrowser } from '@angular/common'; 2 | import { InjectionToken, PLATFORM_ID, inject } from '@angular/core'; 3 | 4 | export const IS_BROWSER = new InjectionToken('IS_BROWSER', { 5 | providedIn: 'root', 6 | factory: () => isPlatformBrowser(inject(PLATFORM_ID)) 7 | }); 8 | -------------------------------------------------------------------------------- /libs/ngu/carousel/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of carousel 3 | */ 4 | 5 | export { NguCarouselConfig } from './lib/ngu-carousel/ngu-carousel'; 6 | 7 | export { NguCarouselStore } from './lib/ngu-carousel/ngu-carousel'; 8 | 9 | export { NguCarousel } from './lib/ngu-carousel/ngu-carousel.component'; 10 | 11 | export { NguItemComponent } from './lib/ngu-item/ngu-item.component'; 12 | 13 | export { NguTileComponent } from './lib/ngu-tile/ngu-tile.component'; 14 | 15 | export { 16 | NguCarouselDefDirective, 17 | NguCarouselPointDirective, 18 | NguCarouselItemDirective, 19 | NguCarouselNextDirective, 20 | NguCarouselPrevDirective, 21 | NguCarouselOutlet 22 | } from './lib/ngu-carousel.directive'; 23 | -------------------------------------------------------------------------------- /libs/ngu/carousel/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import { getTestBed } from '@angular/core/testing'; 4 | import { 5 | BrowserDynamicTestingModule, 6 | platformBrowserDynamicTesting 7 | } from '@angular/platform-browser-dynamic/testing'; 8 | 9 | getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), { 10 | errorOnUnknownElements: true, 11 | errorOnUnknownProperties: true 12 | }); 13 | -------------------------------------------------------------------------------- /libs/ngu/carousel/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | }, 12 | { 13 | "path": "./.storybook/tsconfig.json" 14 | }, 15 | { 16 | "path": "./cypress/tsconfig.json" 17 | } 18 | ], 19 | "compilerOptions": { 20 | "forceConsistentCasingInFileNames": true, 21 | "strict": true, 22 | "strictPropertyInitialization": false, 23 | "noImplicitOverride": true, 24 | "noPropertyAccessFromIndexSignature": true, 25 | "noImplicitReturns": true, 26 | "noFallthroughCasesInSwitch": true, 27 | "target": "es2020" 28 | }, 29 | "angularCompilerOptions": { 30 | "strictInjectionParameters": true, 31 | "strictInputAccessModifiers": true, 32 | "strictTemplates": true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /libs/ngu/carousel/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "declaration": true, 6 | "declarationMap": true, 7 | "inlineSources": true, 8 | "strictNullChecks": true, 9 | "types": [] 10 | }, 11 | "exclude": [ 12 | "src/test.ts", 13 | "**/*.spec.ts", 14 | "cypress/**/*", 15 | "cypress.config.ts", 16 | "**/*.cy.ts", 17 | "**/*.cy.js", 18 | "**/*.cy.tsx", 19 | "**/*.cy.jsx", 20 | "**/*.stories.ts", 21 | "**/*.stories.js" 22 | ], 23 | "include": ["**/*.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /libs/ngu/carousel/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/ngu/carousel/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "types": ["jasmine", "node"] 6 | }, 7 | "files": ["src/test.ts"], 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /lint-staged.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | '**/*.{ts,js,tsx,jsx,mjs,json,md,mdx,scss,html,yml}': f => 3 | `npx nx format:write --files=${f.join(',')}`, 4 | '{apps,libs,tools}/**/*.{ts,js}': 'eslint' 5 | }; 6 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasksRunnerOptions": { 3 | "default": { 4 | "options": { 5 | "runtimeCacheInputs": ["node -v", "node ./scripts/get-os.js"] 6 | } 7 | } 8 | }, 9 | "namedInputs": { 10 | "sharedGlobals": [], 11 | "default": ["{projectRoot}/**/*", "sharedGlobals"], 12 | "production": [ 13 | "default", 14 | "!{projectRoot}/tsconfig.spec.json", 15 | "!{projectRoot}/**/*.spec.[jt]s", 16 | "!{projectRoot}/karma.conf.js", 17 | "!{projectRoot}/.eslintrc.json", 18 | "!{projectRoot}/cypress/**/*", 19 | "!{projectRoot}/**/*.cy.[jt]s?(x)", 20 | "!{projectRoot}/cypress.config.[jt]s", 21 | "!{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)", 22 | "!{projectRoot}/.storybook/**/*", 23 | "!{projectRoot}/tsconfig.storybook.json" 24 | ] 25 | }, 26 | "targetDefaults": { 27 | "build": { 28 | "dependsOn": ["^build"], 29 | "inputs": ["production", "^production"], 30 | "cache": true 31 | }, 32 | "test": { 33 | "inputs": ["default", "^production", "{workspaceRoot}/karma.conf.js"], 34 | "cache": true 35 | }, 36 | "e2e": { 37 | "inputs": ["default", "^production"] 38 | }, 39 | "component-test": { 40 | "inputs": ["default", "^production"], 41 | "cache": true 42 | }, 43 | "build-storybook": { 44 | "inputs": [ 45 | "default", 46 | "^production", 47 | "{projectRoot}/.storybook/**/*", 48 | "{projectRoot}/tsconfig.storybook.json" 49 | ], 50 | "cache": true 51 | }, 52 | "@nx/eslint:lint": { 53 | "inputs": ["default", "{workspaceRoot}/.eslintrc.json"], 54 | "cache": true 55 | } 56 | }, 57 | "cli": { 58 | "analytics": false, 59 | "packageManager": "pnpm", 60 | "schematicCollections": ["@angular-eslint/schematics"] 61 | }, 62 | "generators": { 63 | "@angular-eslint/schematics:application": { 64 | "setParserOptionsProject": true 65 | }, 66 | "@angular-eslint/schematics:library": { 67 | "setParserOptionsProject": true 68 | } 69 | }, 70 | "nxCloudAccessToken": "ZDc5YjI3NDItNzg5OS00YWM4LWE4ODItZjAzNjFkOWUwNzE3fHJlYWQtd3JpdGU=", 71 | "plugins": [ 72 | { 73 | "plugin": "@nx/cypress/plugin", 74 | "options": { 75 | "targetName": "e2e", 76 | "componentTestingTargetName": "component-test" 77 | } 78 | } 79 | ], 80 | "useInferencePlugins": false, 81 | "defaultBase": "master" 82 | } 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngu-carousel-example", 3 | "version": "9.0.0", 4 | "license": "MIT", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/uiuniversal/ngu-carousel.git" 8 | }, 9 | "scripts": { 10 | "ng": "nx", 11 | "start": "nx serve ngu-carousel-example", 12 | "format:write": "nx format:write", 13 | "format:check": "nx format:check", 14 | "dev:ssr": "nx run ngu-carousel-example:serve-ssr" 15 | }, 16 | "private": false, 17 | "dependencies": { 18 | "@angular/animations": "19.0.7", 19 | "@angular/cdk": "19.0.5", 20 | "@angular/common": "19.0.7", 21 | "@angular/compiler": "19.0.7", 22 | "@angular/core": "19.0.7", 23 | "@angular/forms": "19.0.7", 24 | "@angular/material": "19.0.5", 25 | "@angular/platform-browser": "19.0.7", 26 | "@angular/platform-browser-dynamic": "19.0.7", 27 | "@angular/platform-server": "19.0.7", 28 | "@angular/router": "19.0.7", 29 | "@angular/ssr": "19.0.7", 30 | "@ngx-builders/analyze": "4.0.0", 31 | "@nx/angular": "20.3.3", 32 | "@storybook/addon-interactions": "^8.2.8", 33 | "express": "~4.18.2", 34 | "hammerjs": "2.0.8", 35 | "rxjs": "7.8.1", 36 | "tslib": "2.6.2", 37 | "zone.js": "0.15.0" 38 | }, 39 | "devDependencies": { 40 | "@angular-devkit/build-angular": "19.0.7", 41 | "@angular-devkit/core": "19.0.7", 42 | "@angular-devkit/schematics": "19.0.7", 43 | "@angular-eslint/eslint-plugin": "19.0.2", 44 | "@angular-eslint/eslint-plugin-template": "19.0.2", 45 | "@angular-eslint/template-parser": "19.0.2", 46 | "@angular/cli": "~18.0.0", 47 | "@angular/compiler-cli": "19.0.7", 48 | "@angular/language-service": "19.0.7", 49 | "@cypress/webpack-dev-server": "3.8.0", 50 | "@nx/cypress": "20.3.3", 51 | "@nx/eslint": "20.3.3", 52 | "@nx/eslint-plugin": "20.3.3", 53 | "@nx/js": "20.3.3", 54 | "@nx/storybook": "20.3.3", 55 | "@nx/web": "20.3.3", 56 | "@nx/workspace": "20.3.3", 57 | "@schematics/angular": "19.0.7", 58 | "@storybook/addon-essentials": "8.5.1", 59 | "@storybook/angular": "8.5.1", 60 | "@storybook/core-server": "8.5.1", 61 | "@types/express": "~4.17.21", 62 | "@types/hammerjs": "2.0.44", 63 | "@types/jasmine": "5.1.2", 64 | "@types/node": "18.19.74", 65 | "@typescript-eslint/eslint-plugin": "7.18.0", 66 | "@typescript-eslint/parser": "7.18.0", 67 | "@typescript-eslint/utils": "7.18.0", 68 | "autoprefixer": "^10.4.0", 69 | "browser-sync": "^3.0.0", 70 | "cypress": "13.17.0", 71 | "eslint": "8.57.0", 72 | "eslint-config-prettier": "9.0.0", 73 | "eslint-plugin-cypress": "2.15.1", 74 | "eslint-plugin-storybook": "~0.6.15", 75 | "html-webpack-plugin": "~5.5.3", 76 | "husky": "8.0.3", 77 | "jasmine-core": "4.6.0", 78 | "jasmine-spec-reporter": "7.0.0", 79 | "karma": "6.4.2", 80 | "karma-chrome-launcher": "3.2.0", 81 | "karma-coverage": "2.2.1", 82 | "karma-jasmine": "5.1.0", 83 | "karma-jasmine-html-reporter": "2.1.0", 84 | "lint-staged": "~15.1.0", 85 | "ng-packagr": "19.0.1", 86 | "nx": "20.3.3", 87 | "postcss": "~8.4.31", 88 | "postcss-import": "~15.1.0", 89 | "postcss-preset-env": "~9.3.0", 90 | "postcss-url": "~10.1.3", 91 | "prettier": "3.1.0", 92 | "react": "~18.2.0", 93 | "react-dom": "~18.2.0", 94 | "storybook": "8.5.1", 95 | "typescript": "5.6.3", 96 | "webpack": "~5.89.0" 97 | }, 98 | "keywords": [ 99 | "angular", 100 | "carousel", 101 | "universal", 102 | "angular universal", 103 | "universal carousel", 104 | "ngu carousel" 105 | ] 106 | } 107 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"] 3 | } 4 | -------------------------------------------------------------------------------- /scripts/get-os.js: -------------------------------------------------------------------------------- 1 | // This script is used as a runtime input for the Nx cache. 2 | const os = require('os'); 3 | // Things like the version of NodeJS, whether we are running Windows or not, can affect 4 | // the results of the computation but cannot be deduced statically. 5 | console.log(os.type()); 6 | -------------------------------------------------------------------------------- /storybook-migration-summary.md: -------------------------------------------------------------------------------- 1 | # Storybook 7 Migration Summary 2 | 3 | ## Upgrade Storybook packages 4 | 5 | The following command was ran to upgrade the Storybook packages: 6 | 7 | ```bash 8 | npx storybook@latest upgrade 9 | ``` 10 | 11 | ## Your `.storybook/main.js|ts` files were prepared for Storybook's automigration scripts 12 | 13 | Some adjustments were made to your `.storybook/main.js|ts` files so that 14 | the Storybook automigration scripts could run successfully. The changes that were made are as follows: 15 | 16 | - Remove the `as StorybookConfig` typecast from the main.ts files, if any, 17 | since it is not needed any more. 18 | - Remove the `path.resolve` calls from the Next.js Storybook configuration, if any, since it breaks the Storybook automigration scripts. 19 | 20 | ## The Storybook automigration scripts were ran 21 | 22 | The following commands ran successfully and your Storybook configuration was successfully migrated to the latest version 7: 23 | 24 | - `npx storybook@latest automigrate --config-dir libs/ngu/carousel/.storybook --renderer @storybook/angular` 25 | 26 | Please make sure to check the results yourself and make sure that everything is working as expected. 27 | 28 | Also, we may have missed something. Please make sure to check the logs of the Storybook CLI commands that were run, and look for 29 | the `❌ Failed trying to evaluate` message or `❌ The migration failed to update` message. This will indicate if a command was 30 | unsuccessful, and will help you run the migration again, manually. 31 | 32 | ## Final adjustments 33 | 34 | After the Storybook automigration scripts have run, some additional adjustments were made to your 35 | workspace, to make sure that everything is working as expected. These adjustments are as follows: 36 | 37 | - The `vite-tsconfig-paths` plugin was removed from the Storybook configuration files since it's no longer needed. 38 | - The `viteConfigPath` option was added to the Storybook builder, where needed. 39 | - The import package for the `StorybookConfig` type was changed to be framework specific. 40 | - The `uiFramework` option was removed from your project's Storybook targets. 41 | - The `lit` package was added to your workspace, if you are using the 42 | Web Components `@storybook/web-components` package. Please note that the `lit-html` package is 43 | no longer needed by Storybook v7. So, if you are not using it anywhere else, you can safely remove it. 44 | 45 | ## Next steps 46 | 47 | You can make sure everything is working as expected by trying 48 | to build or serve your Storybook as you normally would. 49 | 50 | ```bash 51 | npx nx build-storybook project-name 52 | ``` 53 | 54 | ```bash 55 | npx nx storybook project-name 56 | ``` 57 | 58 | Please read the [Storybook 7.0.0 release article](https://storybook.js.org/blog/storybook-7-0/) and the 59 | official [Storybook 7.0.0 migration guide](https://storybook.js.org/docs/react/migration-guide) 60 | for more information. 61 | 62 | You can also read the docs for the [@nx/storybook:migrate-7 generator](https://nx.dev/packages/storybook/generators/migrate-7) and our [Storybook 7 setup guide](https://nx.dev/packages/storybook/documents/storybook-7-setup). 63 | -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"], 9 | "importHelpers": false 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "./dist/out-tsc", 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": false, 8 | "noImplicitReturns": false, 9 | "noFallthroughCasesInSwitch": true, 10 | "sourceMap": true, 11 | "declaration": false, 12 | "downlevelIteration": true, 13 | "experimentalDecorators": true, 14 | "moduleResolution": "node", 15 | "importHelpers": true, 16 | "target": "ES2022", 17 | "module": "es2020", 18 | "lib": ["es2020", "dom"], 19 | "paths": { 20 | "@ngu/carousel": ["libs/ngu/carousel/src/public-api"] 21 | }, 22 | "useDefineForClassFields": false, 23 | "rootDir": "." 24 | }, 25 | "angularCompilerOptions": { 26 | "enableI18nLegacyMessageIdFormat": false, 27 | "strictInjectionParameters": true, 28 | "strictInputAccessModifiers": true, 29 | "strictTemplates": true 30 | }, 31 | "exclude": ["node_modules", "tmp"] 32 | } 33 | --------------------------------------------------------------------------------