├── .editorconfig ├── .eslintrc.json ├── .github ├── dependabot.yml └── workflows │ ├── main.yml │ ├── plan-release.yml │ └── publish.yml ├── .gitignore ├── .prettierrc.js ├── .release-plan.json ├── .tool-versions ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── RELEASE.md ├── angular.json ├── package-lock.json ├── package.json ├── projects ├── shepherd-tester-e2e │ ├── .eslintrc.json │ ├── protractor.conf.js │ ├── src │ │ ├── app.e2e-spec.ts │ │ └── app.po.ts │ └── tsconfig.e2e.json ├── shepherd-tester │ ├── .eslintrc.json │ ├── browserslist │ ├── karma.conf.js │ ├── src │ │ ├── app │ │ │ ├── app.component.css │ │ │ ├── app.component.html │ │ │ ├── app.component.spec.ts │ │ │ ├── app.component.ts │ │ │ ├── app.module.ts │ │ │ ├── data.ts │ │ │ └── shepherd │ │ │ │ ├── shepherd.component.css │ │ │ │ ├── shepherd.component.html │ │ │ │ ├── shepherd.component.spec.ts │ │ │ │ └── shepherd.component.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.ts │ │ └── styles.css │ ├── tsconfig.app.json │ └── tsconfig.spec.json └── shepherd │ ├── .eslintrc.json │ ├── LICENSE.md │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ ├── lib │ │ ├── shepherd.service.spec.ts │ │ ├── shepherd.service.ts │ │ └── utils │ │ │ ├── buttons.ts │ │ │ └── dom.ts │ └── public_api.ts │ ├── tsconfig.lib.json │ └── tsconfig.spec.json └── tsconfig.json /.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 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": [ 4 | "projects/**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "parserOptions": { 12 | "project": [ 13 | "tsconfig.json" 14 | ], 15 | "createDefaultProgram": true 16 | }, 17 | "extends": [ 18 | "plugin:@angular-eslint/recommended", 19 | "plugin:@angular-eslint/template/process-inline-templates" 20 | ], 21 | "rules": { 22 | "@angular-eslint/component-selector": [ 23 | "error", 24 | { 25 | "prefix": "lib", 26 | "style": "kebab-case", 27 | "type": "element" 28 | } 29 | ], 30 | "@angular-eslint/directive-selector": [ 31 | "error", 32 | { 33 | "prefix": "lib", 34 | "style": "camelCase", 35 | "type": "attribute" 36 | } 37 | ] 38 | } 39 | }, 40 | { 41 | "files": [ 42 | "*.html" 43 | ], 44 | "extends": [ 45 | "plugin:@angular-eslint/template/recommended" 46 | ], 47 | "rules": {} 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "10:00" 8 | open-pull-requests-limit: 10 9 | labels: 10 | - dependencies 11 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI Build 2 | 3 | on: 4 | pull_request: {} 5 | push: 6 | branches: 7 | - master 8 | - main 9 | tags: 10 | - v* 11 | 12 | jobs: 13 | test: 14 | name: Tests 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: volta-cli/action@v4 20 | - run: npm install 21 | - run: npm run lint 22 | - run: npm run test -- --no-watch --no-progress --browsers=ChromeHeadless 23 | automerge: 24 | needs: [test] 25 | runs-on: ubuntu-latest 26 | permissions: 27 | pull-requests: write 28 | contents: write 29 | steps: 30 | - uses: fastify/github-action-merge-dependabot@v3.10.1 31 | with: 32 | github-token: ${{secrets.GITHUB_TOKEN}} 33 | -------------------------------------------------------------------------------- /.github/workflows/plan-release.yml: -------------------------------------------------------------------------------- 1 | name: Release Plan Review 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - master 7 | pull_request_target: # This workflow has permissions on the repo, do NOT run code from PRs in this workflow. See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ 8 | types: 9 | - labeled 10 | - unlabeled 11 | 12 | concurrency: 13 | group: plan-release # only the latest one of these should ever be running 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | check-plan: 18 | name: "Check Release Plan" 19 | runs-on: ubuntu-latest 20 | outputs: 21 | command: ${{ steps.check-release.outputs.command }} 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 0 27 | ref: 'main' 28 | # This will only cause the `check-plan` job to have a "command" of `release` 29 | # when the .release-plan.json file was changed on the last commit. 30 | - id: check-release 31 | run: if git diff --name-only HEAD HEAD~1 | grep -w -q ".release-plan.json"; then echo "command=release"; fi >> $GITHUB_OUTPUT 32 | 33 | prepare_release_notes: 34 | name: Prepare Release Notes 35 | runs-on: ubuntu-latest 36 | timeout-minutes: 5 37 | needs: check-plan 38 | permissions: 39 | contents: write 40 | pull-requests: write 41 | outputs: 42 | explanation: ${{ steps.explanation.outputs.text }} 43 | # only run on push event if plan wasn't updated (don't create a release plan when we're releasing) 44 | # only run on labeled event if the PR has already been merged 45 | if: (github.event_name == 'push' && needs.check-plan.outputs.command != 'release') || (github.event_name == 'pull_request_target' && github.event.pull_request.merged == true) 46 | 47 | steps: 48 | - uses: actions/checkout@v4 49 | # We need to download lots of history so that 50 | # github-changelog can discover what's changed since the last release 51 | with: 52 | fetch-depth: 0 53 | ref: 'main' 54 | - uses: actions/setup-node@v4 55 | with: 56 | node-version: 18 57 | 58 | - run: npm ci 59 | 60 | - name: "Generate Explanation and Prep Changelogs" 61 | id: explanation 62 | run: | 63 | set +e 64 | 65 | npx release-plan prepare 2> >(tee -a release-plan-stderr.txt >&2) 66 | 67 | 68 | if [ $? -ne 0 ]; then 69 | echo 'text<> $GITHUB_OUTPUT 70 | cat release-plan-stderr.txt >> $GITHUB_OUTPUT 71 | echo 'EOF' >> $GITHUB_OUTPUT 72 | else 73 | echo 'text<> $GITHUB_OUTPUT 74 | jq .description .release-plan.json -r >> $GITHUB_OUTPUT 75 | echo 'EOF' >> $GITHUB_OUTPUT 76 | rm release-plan-stderr.txt 77 | fi 78 | env: 79 | GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }} 80 | 81 | - uses: peter-evans/create-pull-request@v6 82 | with: 83 | commit-message: "Prepare Release using 'release-plan'" 84 | labels: "internal" 85 | branch: release-preview 86 | title: Prepare Release 87 | body: | 88 | This PR is a preview of the release that [release-plan](https://github.com/embroider-build/release-plan) has prepared. To release you should just merge this PR 👍 89 | 90 | ----------------------------------------- 91 | 92 | ${{ steps.explanation.outputs.text }} 93 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # For every push to the master branch, this checks if the release-plan was 2 | # updated and if it was it will publish stable npm packages based on the 3 | # release plan 4 | 5 | name: Publish Stable 6 | 7 | on: 8 | workflow_dispatch: 9 | push: 10 | branches: 11 | - main 12 | - master 13 | 14 | concurrency: 15 | group: publish-${{ github.head_ref || github.ref }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | check-plan: 20 | name: "Check Release Plan" 21 | runs-on: ubuntu-latest 22 | outputs: 23 | command: ${{ steps.check-release.outputs.command }} 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | with: 28 | fetch-depth: 0 29 | ref: 'main' 30 | # This will only cause the `check-plan` job to have a result of `success` 31 | # when the .release-plan.json file was changed on the last commit. This 32 | # plus the fact that this action only runs on main will be enough of a guard 33 | - id: check-release 34 | run: if git diff --name-only HEAD HEAD~1 | grep -w -q ".release-plan.json"; then echo "command=release"; fi >> $GITHUB_OUTPUT 35 | 36 | publish: 37 | name: "NPM Publish" 38 | runs-on: ubuntu-latest 39 | needs: check-plan 40 | if: needs.check-plan.outputs.command == 'release' 41 | permissions: 42 | contents: write 43 | pull-requests: write 44 | 45 | steps: 46 | - uses: actions/checkout@v4 47 | - uses: actions/setup-node@v4 48 | with: 49 | node-version: 18 50 | # This creates an .npmrc that reads the NODE_AUTH_TOKEN environment variable 51 | registry-url: 'https://registry.npmjs.org' 52 | 53 | - run: npm ci 54 | - name: npm publish 55 | run: npx release-plan publish 56 | 57 | env: 58 | GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }} 59 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 60 | -------------------------------------------------------------------------------- /.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 | 8 | # dependencies 9 | /node_modules 10 | 11 | # profiling files 12 | chrome-profiler-events.json 13 | speed-measure-plugin.json 14 | 15 | # IDEs and editors 16 | /.idea 17 | .project 18 | .classpath 19 | .c9/ 20 | *.launch 21 | .settings/ 22 | *.sublime-workspace 23 | 24 | # IDE - VSCode 25 | .vscode/* 26 | !.vscode/settings.json 27 | !.vscode/tasks.json 28 | !.vscode/launch.json 29 | !.vscode/extensions.json 30 | .history/* 31 | 32 | # misc 33 | /.angular/cache 34 | /.sass-cache 35 | /connect.lock 36 | /coverage 37 | /libpeerconnection.log 38 | npm-debug.log 39 | yarn-error.log 40 | testem.log 41 | /typings 42 | 43 | # System Files 44 | .DS_Store 45 | Thumbs.db 46 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | arrowParens: "always", 5 | proseWrap: "always", 6 | trailingComma: "none", 7 | singleQuote: true, 8 | }; 9 | -------------------------------------------------------------------------------- /.release-plan.json: -------------------------------------------------------------------------------- 1 | { 2 | "solution": {}, 3 | "description": "## Release (2024-07-10)\n\n\n\n#### :boom: Breaking Change\n* `angular-shepherd`\n * [#2243](https://github.com/shepherd-pro/angular-shepherd/pull/2243) update angular 18 ([@bastienmoulia](https://github.com/bastienmoulia))\n * [#2113](https://github.com/shepherd-pro/angular-shepherd/pull/2113) [feature] increase to angular 17 ([@phhbr](https://github.com/phhbr))\n\n#### :memo: Documentation\n* `angular-shepherd`\n * [#2245](https://github.com/shepherd-pro/angular-shepherd/pull/2245) Update README.md with latest doc links ([@sardul3](https://github.com/sardul3))\n\n#### :house: Internal\n* `angular-shepherd`\n * [#1962](https://github.com/shepherd-pro/angular-shepherd/pull/1962) Update package lock ([@RobbieTheWagner](https://github.com/RobbieTheWagner))\n\n#### Committers: 4\n- Bastien ([@bastienmoulia](https://github.com/bastienmoulia))\n- Phhbr ([@phhbr](https://github.com/phhbr))\n- Robbie Wagner ([@RobbieTheWagner](https://github.com/RobbieTheWagner))\n- Sagar Poudel ([@sardul3](https://github.com/sardul3))\n" 4 | } 5 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 20.15.1 -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | 6 | ## v19.0.2 (2025-01-29) 7 | 8 | #### :bug: Bug Fix 9 | * [#2609](https://github.com/shipshapecode/angular-shepherd/pull/2609) Update types for `exitOnEsc` and `tourName` ([@RobbieTheWagner](https://github.com/RobbieTheWagner)) 10 | 11 | #### Committers: 1 12 | - Robbie Wagner ([@RobbieTheWagner](https://github.com/RobbieTheWagner)) 13 | 14 | ## v19.0.1 (2025-01-28) 15 | 16 | ## v19.0.0 (2025-01-28) 17 | 18 | #### :boom: Breaking Change 19 | * [#2606](https://github.com/shipshapecode/angular-shepherd/pull/2606) Update to Shepherd 14.x ([@RobbieTheWagner](https://github.com/RobbieTheWagner)) 20 | * [#2566](https://github.com/shipshapecode/angular-shepherd/pull/2566) bump-angular-version-to-19 ([@akhromets](https://github.com/akhromets)) 21 | 22 | #### Committers: 2 23 | - Oleksii Khromets ([@akhromets](https://github.com/akhromets)) 24 | - Robbie Wagner ([@RobbieTheWagner](https://github.com/RobbieTheWagner)) 25 | 26 | ## v18.0.2 (2024-07-10) 27 | 28 | ## v18.0.1 (2024-07-10) 29 | 30 | ## v18.0.0 (2024-07-10) 31 | 32 | #### :boom: Breaking Change 33 | * [#2243](https://github.com/shepherd-pro/angular-shepherd/pull/2243) update angular 18 ([@bastienmoulia](https://github.com/bastienmoulia)) 34 | * [#2113](https://github.com/shepherd-pro/angular-shepherd/pull/2113) [feature] increase to angular 17 ([@phhbr](https://github.com/phhbr)) 35 | 36 | #### :memo: Documentation 37 | * [#2245](https://github.com/shepherd-pro/angular-shepherd/pull/2245) Update README.md with latest doc links ([@sardul3](https://github.com/sardul3)) 38 | 39 | #### :house: Internal 40 | * [#2248](https://github.com/shepherd-pro/angular-shepherd/pull/2248) Prepare Release ([@github-actions[bot]](https://github.com/apps/github-actions)) 41 | * [#1962](https://github.com/shepherd-pro/angular-shepherd/pull/1962) Update package lock ([@RobbieTheWagner](https://github.com/RobbieTheWagner)) 42 | 43 | #### Committers: 5 44 | - Bastien ([@bastienmoulia](https://github.com/bastienmoulia)) 45 | - Phhbr ([@phhbr](https://github.com/phhbr)) 46 | - Robbie Wagner ([@RobbieTheWagner](https://github.com/RobbieTheWagner)) 47 | - Sagar Poudel ([@sardul3](https://github.com/sardul3)) 48 | - [@github-actions[bot]](https://github.com/apps/github-actions) -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-shepherd 2 | 3 | Ship Shape 4 | 5 | **[angular-shepherd is built and maintained by Ship Shape. Contact us for web app consulting, development, and training for your project](https://shipshape.io/)**. 6 | 7 | [![npm version](https://badge.fury.io/js/angular-shepherd.svg)](http://badge.fury.io/js/angular-shepherd) ![Download count all time](https://img.shields.io/npm/dt/angular-shepherd.svg) [![npm](https://img.shields.io/npm/dm/angular-shepherd.svg)]() ![CI Build](https://github.com/shipshapecode/angular-shepherd/workflows/CI%20Build/badge.svg) 8 | 9 | This is an Angular wrapper for the [Shepherd](https://github.com/shipshapecode/shepherd), site tour, library. It provides additional functionality, on top of Shepherd, as well. 10 | 11 | ## Compatibility 12 | 13 | - Angular 8: 0.5.0 14 | - Angular 9: 0.6.0 15 | - Angular 10: 0.7.0 16 | - Angular 11: 11.x 17 | - Angular 12: 12.x 18 | - Angular 13: 13.x 19 | - Angular 14: 14.x 20 | - Angular 15: 15.x 21 | - Angular 16: 16.x 22 | - Angular 17: 17.x 23 | - Angular 18: 18.x 24 | - Angular 19: 19.x 25 | 26 | This has not been tested in anything but Angular 8+. It may or may not work in previous versions or subsequent versions of Angular. We would love to support multiple versions, if people with more Angular knowledge would be willing to help us out! 27 | 28 | ## Installation 29 | 30 | ```bash 31 | npm install angular-shepherd --save 32 | ``` 33 | 34 | ## Usage 35 | 36 | **NOTE: This may not be the proper Angular way to do everything, as I am not an Angular dev, so please let me know if you have suggestions!** 37 | 38 | Shepherd ships a single style file, which you will need to include. You can do so by adding it to your angular.json. 39 | 40 | ```json 41 | "styles": [ 42 | "node_modules/shepherd.js/dist/css/shepherd.css" 43 | ], 44 | ``` 45 | 46 | Then, you will need to inject the `ShepherdService` to be able to interact with Shepherd and call `addSteps` to add your steps, `start` to start the tour, etc. 47 | 48 | You could either do this at the application level, in your application component or on a per component or per route/view basis. 49 | 50 | **NOTE: It is highly recommended to inject ShepherdService into your `app.component.ts`. Injecting it at the app level ensures you only create one instance of Shepherd.** 51 | 52 | In that component you will want to use `AfterViewInit` to `addSteps` to the Shepherd service. 53 | 54 | ```typescript 55 | import { Component, type AfterViewInit } from '@angular/core'; 56 | import { ShepherdService } from 'angular-shepherd'; 57 | import { steps as defaultSteps, defaultStepOptions } from '../data'; 58 | 59 | @Component({ 60 | selector: 'shepherd', 61 | templateUrl: './shepherd.component.html', 62 | styleUrls: ['./shepherd.component.css'] 63 | }) 64 | export class ShepherdComponent implements AfterViewInit { 65 | constructor(private shepherdService: ShepherdService) {} 66 | 67 | ngAfterViewInit() { 68 | this.shepherdService.defaultStepOptions = defaultStepOptions; 69 | this.shepherdService.modal = true; 70 | this.shepherdService.confirmCancel = false; 71 | this.shepherdService.addSteps(defaultSteps); 72 | this.shepherdService.start(); 73 | } 74 | } 75 | ``` 76 | 77 | ## Configuration 78 | 79 | The following configuration options can be set on the ShepherdService to control the way that Shepherd is used. **The only required option is `steps`, which is set via `addSteps`.** 80 | 81 | ### confirmCancel 82 | 83 | `confirmCancel` is a boolean flag, when set to `true` it will pop up a native browser confirm window on cancel, to ensure you want to cancel. 84 | 85 | ### confirmCancelMessage 86 | 87 | `confirmCancelMessage` is a string to display in the confirm dialog when `confirmCancel` is set to true. 88 | 89 | ### defaultStepOptions 90 | 91 | `defaultStepOptions` is used to set the options that will be applied to each step by default. You can pass in any of the options that you can with Shepherd. 92 | 93 | **⚠️ You must set `defaultStepOptions` _BEFORE_ calling `addSteps` to set the steps.** 94 | 95 | It will be an object of a form something like: 96 | 97 | ```js 98 | this.shepherdService.defaultStepOptions = { 99 | classes: 'custom-class-name-1 custom-class-name-2', 100 | scrollTo: false, 101 | cancelIcon: { 102 | enabled: true 103 | } 104 | }; 105 | ``` 106 | 107 | > **default value:** `{}` 108 | 109 | ### requiredElements 110 | 111 | `requiredElements` is an array of objects that indicate DOM elements that are **REQUIRED** by your tour and must exist and be visible for the tour to start. If any elements are not present, it will keep the tour from starting. 112 | 113 | You can also specify a message, which will tell the user what they need to do to make the tour work. 114 | 115 | **⚠️ You must set `requiredElements` _BEFORE_ calling `addSteps` to set the steps.** 116 | 117 | _Example_ 118 | 119 | ```js 120 | this.shepherdService.requiredElements = [ 121 | { 122 | selector: '.search-result-element', 123 | message: 'No search results found. Please execute another search, and try to start the tour again.', 124 | title: 'No results' 125 | }, 126 | { 127 | selector: '.username-element', 128 | message: 'User not logged in, please log in to start this tour.', 129 | title: 'Please login' 130 | } 131 | ]; 132 | ``` 133 | 134 | > **default value:** `[]` 135 | 136 | ### modal 137 | 138 | `modal` is a boolean, that should be set to true, if you would like the rest of the screen, other than the current element, greyed out, and the current element highlighted. If you do not need modal functionality, you can remove this option or set it to false. 139 | 140 | > **default value:** `false` 141 | 142 | ### addSteps 143 | 144 | You must pass an array of steps to `addSteps`, something like this: 145 | 146 | ```js 147 | this.shepherdService.addSteps([ 148 | { 149 | id: 'intro', 150 | attachTo: { 151 | element: '.first-element', 152 | on: 'bottom' 153 | }, 154 | beforeShowPromise: function() { 155 | return new Promise(function(resolve) { 156 | setTimeout(function() { 157 | window.scrollTo(0, 0); 158 | resolve(); 159 | }, 500); 160 | }); 161 | }, 162 | buttons: [ 163 | { 164 | classes: 'shepherd-button-secondary', 165 | text: 'Exit', 166 | type: 'cancel' 167 | }, 168 | { 169 | classes: 'shepherd-button-primary', 170 | text: 'Back', 171 | type: 'back' 172 | }, 173 | { 174 | classes: 'shepherd-button-primary', 175 | text: 'Next', 176 | type: 'next' 177 | } 178 | ], 179 | cancelIcon: { 180 | enabled: true 181 | }, 182 | classes: 'custom-class-name-1 custom-class-name-2', 183 | highlightClass: 'highlight', 184 | scrollTo: false, 185 | title: 'Welcome to Angular-Shepherd!', 186 | text: ['Angular-Shepherd is a JavaScript library for guiding users through your Angular app.'], 187 | when: { 188 | show: () => { 189 | console.log('show step'); 190 | }, 191 | hide: () => { 192 | console.log('hide step'); 193 | } 194 | } 195 | }, 196 | ... 197 | ]); 198 | ``` 199 | 200 | ## Buttons 201 | 202 | In Shepherd, you can have as many buttons as you want inside a step. You can build an object with some premade buttons, making it easier to manipulate and insert in new steps. Buttons by default accept three different types: back, cancel, next. In this simple example, we have three buttons: each one with different types and classes. 203 | 204 | ```js 205 | const builtInButtons = { 206 | cancel: { 207 | classes: 'cancel-button', 208 | text: 'Cancel', 209 | type: 'cancel' 210 | }, 211 | next: { 212 | classes: 'next-button', 213 | text: 'Next', 214 | type: 'next' 215 | }, 216 | back: { 217 | classes: 'back-button', 218 | secondary: true, 219 | text: 'Back', 220 | type: 'back' 221 | } 222 | }; 223 | ``` 224 | 225 | Buttons have an action property, which must be a function. Whenever the button is clicked, the function will be executed. You can use it for default shepherd functions, like `this.shepherdService.complete()` or `this.shepherdService.next()`, or create your own function to use for the action. 226 | 227 | ```js 228 | const builtInButtons = { 229 | complete: { 230 | classes: 'complete-button', 231 | text: 'Finish Tutorial', 232 | action: function () { 233 | return console.log('button clicked'); 234 | } 235 | } 236 | }; 237 | ``` 238 | 239 | **⚠️ You can't set up a type and an action at the same time inside a button**. 240 | 241 | To learn more about button properties, look at the [documentation](https://docs.shepherdjs.dev/api/step/interfaces/stepoptionsbutton/). 242 | 243 | ## Step Options 244 | 245 | See the [Step docs](https://docs.shepherdjs.dev/api/step/classes/step/) for all available Step options. 246 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | Releases in this repo are mostly automated using [release-plan](https://github.com/embroider-build/release-plan/). Once you label all your PRs correctly (see below) you will have an automatically generated PR that updates your CHANGELOG.md file and a `.release-plan.json` that is used to prepare the release once the PR is merged. 4 | 5 | ## Preparation 6 | 7 | Since the majority of the actual release process is automated, the remaining tasks before releasing are: 8 | 9 | - correctly labeling **all** pull requests that have been merged since the last release 10 | - updating pull request titles so they make sense to our users 11 | 12 | Some great information on why this is important can be found at [keepachangelog.com](https://keepachangelog.com/en/1.1.0/), but the overall 13 | guiding principle here is that changelogs are for humans, not machines. 14 | 15 | When reviewing merged PR's the labels to be used are: 16 | 17 | * breaking - Used when the PR is considered a breaking change. 18 | * enhancement - Used when the PR adds a new feature or enhancement. 19 | * bug - Used when the PR fixes a bug included in a previous release. 20 | * documentation - Used when the PR adds or updates documentation. 21 | * internal - Internal changes or things that don't fit in any other category. 22 | 23 | **Note:** `release-plan` requires that **all** PRs are labeled. If a PR doesn't fit in a category it's fine to label it as `internal` 24 | 25 | ## Release 26 | 27 | Once the prep work is completed, the actual release is straight forward: you just need to merge the open [Plan Release](https://github.com/shepherd-pro/angular-shepherd/pulls?q=is%3Apr+is%3Aopen+%22Prepare+Release%22+in%3Atitle) PR 28 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "shepherd": { 7 | "root": "projects/shepherd", 8 | "sourceRoot": "projects/shepherd/src", 9 | "projectType": "library", 10 | "prefix": "lib", 11 | "architect": { 12 | "build": { 13 | "builder": "@angular-devkit/build-angular:ng-packagr", 14 | "options": { 15 | "tsConfig": "projects/shepherd/tsconfig.lib.json", 16 | "project": "projects/shepherd/ng-package.json" 17 | } 18 | }, 19 | "test": { 20 | "builder": "@angular-devkit/build-angular:karma", 21 | "options": { 22 | "polyfills": [ 23 | "zone.js", 24 | "zone.js/testing" 25 | ], 26 | "tsConfig": "projects/shepherd/tsconfig.spec.json", 27 | "karmaConfig": "projects/shepherd/karma.conf.js" 28 | } 29 | }, 30 | "lint": { 31 | "builder": "@angular-eslint/builder:lint", 32 | "options": { 33 | "lintFilePatterns": [ 34 | "projects/shepherd/**/*.ts", 35 | "projects/shepherd/**/*.html" 36 | ] 37 | } 38 | } 39 | } 40 | }, 41 | "shepherd-tester": { 42 | "root": "projects/shepherd-tester/", 43 | "sourceRoot": "projects/shepherd-tester/src", 44 | "projectType": "application", 45 | "prefix": "app", 46 | "schematics": {}, 47 | "architect": { 48 | "build": { 49 | "builder": "@angular-devkit/build-angular:browser", 50 | "options": { 51 | "outputPath": "dist/shepherd-tester", 52 | "index": "projects/shepherd-tester/src/index.html", 53 | "main": "projects/shepherd-tester/src/main.ts", 54 | "polyfills": ["zone.js"], 55 | "tsConfig": "projects/shepherd-tester/tsconfig.app.json", 56 | "assets": [ 57 | "projects/shepherd-tester/src/favicon.ico", 58 | "projects/shepherd-tester/src/assets" 59 | ], 60 | "styles": [ 61 | "node_modules/shepherd.js/dist/css/shepherd.css", 62 | "projects/shepherd-tester/src/styles.css" 63 | ], 64 | "scripts": [] 65 | }, 66 | "configurations": { 67 | "production": { 68 | "fileReplacements": [ 69 | { 70 | "replace": "projects/shepherd-tester/src/environments/environment.ts", 71 | "with": "projects/shepherd-tester/src/environments/environment.prod.ts" 72 | } 73 | ], 74 | "optimization": true, 75 | "outputHashing": "all", 76 | "sourceMap": false, 77 | "namedChunks": false, 78 | "aot": true, 79 | "extractLicenses": true, 80 | "vendorChunk": false, 81 | "buildOptimizer": true, 82 | "budgets": [ 83 | { 84 | "type": "initial", 85 | "maximumWarning": "2mb", 86 | "maximumError": "5mb" 87 | } 88 | ] 89 | } 90 | } 91 | }, 92 | "serve": { 93 | "builder": "@angular-devkit/build-angular:dev-server", 94 | "options": { 95 | "buildTarget": "shepherd-tester:build" 96 | }, 97 | "configurations": { 98 | "production": { 99 | "buildTarget": "shepherd-tester:build:production" 100 | } 101 | } 102 | }, 103 | "extract-i18n": { 104 | "builder": "@angular-devkit/build-angular:extract-i18n", 105 | "options": { 106 | "buildTarget": "shepherd-tester:build" 107 | } 108 | }, 109 | "test": { 110 | "builder": "@angular-devkit/build-angular:karma", 111 | "options": { 112 | "polyfills": [ 113 | "zone.js", 114 | "zone.js/testing" 115 | ], 116 | "tsConfig": "projects/shepherd-tester/tsconfig.spec.json", 117 | "karmaConfig": "projects/shepherd-tester/karma.conf.js", 118 | "styles": [ 119 | "projects/shepherd-tester/src/styles.css" 120 | ], 121 | "scripts": [], 122 | "assets": [ 123 | "projects/shepherd-tester/src/favicon.ico", 124 | "projects/shepherd-tester/src/assets" 125 | ] 126 | } 127 | }, 128 | "lint": { 129 | "builder": "@angular-eslint/builder:lint", 130 | "options": { 131 | "lintFilePatterns": [ 132 | "projects/shepherd-tester//**/*.ts", 133 | "projects/shepherd-tester//**/*.html" 134 | ] 135 | } 136 | } 137 | } 138 | }, 139 | "shepherd-tester-e2e": { 140 | "root": "projects/shepherd-tester-e2e/", 141 | "projectType": "application", 142 | "prefix": "", 143 | "architect": { 144 | "e2e": { 145 | "builder": "@angular-devkit/build-angular:protractor", 146 | "options": { 147 | "protractorConfig": "projects/shepherd-tester-e2e/protractor.conf.js", 148 | "devServerTarget": "shepherd-tester:serve" 149 | }, 150 | "configurations": { 151 | "production": { 152 | "devServerTarget": "shepherd-tester:serve:production" 153 | } 154 | } 155 | }, 156 | "lint": { 157 | "builder": "@angular-eslint/builder:lint", 158 | "options": { 159 | "lintFilePatterns": [ 160 | "projects/shepherd-tester-e2e//**/*.ts", 161 | "projects/shepherd-tester-e2e//**/*.html" 162 | ] 163 | } 164 | } 165 | } 166 | } 167 | }, 168 | "cli": { 169 | "analytics": "02495330-e062-4d0b-8606-581c26727064", 170 | "schematicCollections": [ 171 | "@angular-eslint/schematics", 172 | "@angular-eslint/schematics", 173 | "@angular-eslint/schematics" 174 | ] 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-shepherd", 3 | "private": true, 4 | "repository": { 5 | "type": "git", 6 | "url": "git@github.com:shepherd-pro/angular-shepherd.git" 7 | }, 8 | "scripts": { 9 | "build": "ng build", 10 | "build_lib": "ng build shepherd", 11 | "e2e": "ng e2e", 12 | "lint": "ng lint", 13 | "ng": "ng", 14 | "npm_pack": "cd dist/angular-shepherd && npm pack", 15 | "package": "npm run build_lib && npm run npm_pack", 16 | "start": "ng serve", 17 | "test": "ng test" 18 | }, 19 | "dependencies": { 20 | "@angular/animations": "^19.1.4", 21 | "@angular/common": "^19.0.0", 22 | "@angular/compiler": "^19.0.0", 23 | "@angular/core": "^19.1.4", 24 | "@angular/platform-browser": "^19.1.4", 25 | "@angular/platform-browser-dynamic": "^19.1.4", 26 | "core-js": "^3.39.0", 27 | "rxjs": "^7.8.1", 28 | "shepherd.js": "^14.4.0", 29 | "tslib": "^2.8.0", 30 | "zone.js": "~0.15.0" 31 | }, 32 | "devDependencies": { 33 | "@angular-devkit/build-angular": "^19.1.6", 34 | "@angular-devkit/core": "^19.0.0", 35 | "@angular-devkit/schematics": "^19.1.6", 36 | "@angular-eslint/builder": "19.0.2", 37 | "@angular-eslint/eslint-plugin": "19.0.2", 38 | "@angular-eslint/eslint-plugin-template": "19.0.2", 39 | "@angular-eslint/schematics": "^19.0.0", 40 | "@angular-eslint/template-parser": "^19.0.0", 41 | "@angular/cli": "^19.1.6", 42 | "@angular/compiler-cli": "^19.1.4", 43 | "@angular/language-service": "^19.1.5", 44 | "@angular/router": "^19.1.4", 45 | "@release-it-plugins/lerna-changelog": "^7.0.0", 46 | "@release-it/bumper": "^6.0.1", 47 | "@types/jasmine": "^5.1.4", 48 | "@types/jasminewd2": "^2.0.13", 49 | "@types/node": "^22.13.1", 50 | "@typescript-eslint/eslint-plugin": "7.18", 51 | "@typescript-eslint/parser": "7.18", 52 | "eslint": "^8.57.1", 53 | "jasmine-core": "^5.4.0", 54 | "jasmine-spec-reporter": "^7.0.0", 55 | "karma": "^6.4.4", 56 | "karma-chrome-launcher": "^3.2.0", 57 | "karma-coverage-istanbul-reporter": "^3.0.3", 58 | "karma-jasmine": "^5.1.0", 59 | "karma-jasmine-html-reporter": "^2.1.0", 60 | "ng-packagr": "^19.1.2", 61 | "prettier": "^3.4.2", 62 | "protractor": "~7.0.0", 63 | "release-it": "^17.10.0", 64 | "ts-node": "~10.9.2", 65 | "typescript": "~5.7.3" 66 | }, 67 | "volta": { 68 | "node": "20.15.1" 69 | }, 70 | "publishConfig": { 71 | "registry": "https://registry.npmjs.org" 72 | }, 73 | "release-it": { 74 | "plugins": { 75 | "@release-it/bumper": { 76 | "out": "projects/shepherd/package.json" 77 | }, 78 | "@release-it-plugins/lerna-changelog": { 79 | "infile": "CHANGELOG.md", 80 | "launchEditor": true 81 | } 82 | }, 83 | "git": { 84 | "tagName": "v${version}" 85 | }, 86 | "github": { 87 | "release": true, 88 | "tokenRef": "GITHUB_AUTH" 89 | }, 90 | "npm": false 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /projects/shepherd-tester-e2e/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "ignorePatterns": [ 4 | "!**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "parserOptions": { 12 | "project": [ 13 | "projects/shepherd-tester-e2e//tsconfig.(app|spec).json" 14 | ] 15 | }, 16 | "rules": { 17 | "@angular-eslint/directive-selector": [ 18 | "error", 19 | { 20 | "type": "attribute", 21 | "prefix": "", 22 | "style": "camelCase" 23 | } 24 | ], 25 | "@angular-eslint/component-selector": [ 26 | "error", 27 | { 28 | "type": "element", 29 | "prefix": "", 30 | "style": "kebab-case" 31 | } 32 | ] 33 | } 34 | }, 35 | { 36 | "files": [ 37 | "*.html" 38 | ], 39 | "rules": {} 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /projects/shepherd-tester-e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /projects/shepherd-tester-e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to shepherd-tester!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | })); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /projects/shepherd-tester-e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /projects/shepherd-tester-e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /projects/shepherd-tester/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "ignorePatterns": [ 4 | "!**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "parserOptions": { 12 | "project": [ 13 | "projects/shepherd-tester//tsconfig.(app|spec).json" 14 | ] 15 | }, 16 | "rules": { 17 | "@angular-eslint/directive-selector": [ 18 | "error", 19 | { 20 | "type": "attribute", 21 | "prefix": "app", 22 | "style": "camelCase" 23 | } 24 | ], 25 | "@angular-eslint/component-selector": [ 26 | "error", 27 | { 28 | "type": "element", 29 | "prefix": "app", 30 | "style": "kebab-case" 31 | } 32 | ] 33 | } 34 | }, 35 | { 36 | "files": [ 37 | "*.html" 38 | ], 39 | "rules": {} 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /projects/shepherd-tester/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /projects/shepherd-tester/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 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../../coverage/shepherd-tester'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /projects/shepherd-tester/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shipshapecode/angular-shepherd/d850120a9b66b907039c4d70f3bac365f668e2fb/projects/shepherd-tester/src/app/app.component.css -------------------------------------------------------------------------------- /projects/shepherd-tester/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /projects/shepherd-tester/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, waitForAsync } from "@angular/core/testing"; 2 | import { AppComponent } from "./app.component"; 3 | import { ShepherdComponent } from "./shepherd/shepherd.component"; 4 | 5 | describe("AppComponent", () => { 6 | beforeEach(waitForAsync(() => { 7 | TestBed.configureTestingModule({ 8 | imports: [AppComponent, ShepherdComponent], 9 | }).compileComponents(); 10 | })); 11 | 12 | it("should create the app", () => { 13 | const fixture = TestBed.createComponent(AppComponent); 14 | const app = fixture.debugElement.componentInstance; 15 | expect(app).toBeTruthy(); 16 | }); 17 | 18 | it(`should have as title 'shepherd-tester'`, () => { 19 | const fixture = TestBed.createComponent(AppComponent); 20 | const app = fixture.debugElement.componentInstance; 21 | expect(app.title).toEqual("shepherd-tester"); 22 | }); 23 | 24 | // it('should render title in a h1 tag', () => { 25 | // const fixture = TestBed.createComponent(AppComponent); 26 | // fixture.detectChanges(); 27 | // const compiled = fixture.debugElement.nativeElement; 28 | // expect(compiled.querySelector('h1').textContent).toContain('Welcome to shepherd-tester!'); 29 | // }); 30 | }); 31 | -------------------------------------------------------------------------------- /projects/shepherd-tester/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import {ShepherdComponent} from "./shepherd/shepherd.component"; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.css'], 8 | standalone: true, 9 | imports: [ 10 | ShepherdComponent 11 | ] 12 | }) 13 | export class AppComponent { 14 | title = 'shepherd-tester'; 15 | } 16 | -------------------------------------------------------------------------------- /projects/shepherd-tester/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { AppComponent } from './app.component'; 5 | import { ShepherdComponent } from './shepherd/shepherd.component'; 6 | 7 | @NgModule({ 8 | declarations: [ 9 | AppComponent, 10 | ShepherdComponent 11 | ], 12 | imports: [ 13 | BrowserModule 14 | ], 15 | providers: [], 16 | bootstrap: [AppComponent] 17 | }) 18 | export class AppModule { } 19 | -------------------------------------------------------------------------------- /projects/shepherd-tester/src/app/data.ts: -------------------------------------------------------------------------------- 1 | import { type StepOptions } from 'shepherd.js'; 2 | 3 | export const builtInButtons = { 4 | cancel: { 5 | classes: 'cancel-button', 6 | secondary: true, 7 | text: 'Exit', 8 | type: 'cancel' 9 | }, 10 | next: { 11 | classes: 'next-button', 12 | text: 'Next', 13 | type: 'next' 14 | }, 15 | back: { 16 | classes: 'back-button', 17 | secondary: true, 18 | text: 'Back', 19 | type: 'back' 20 | } 21 | }; 22 | 23 | export const defaultStepOptions: StepOptions = { 24 | classes: 'shepherd-theme-arrows custom-default-class', 25 | scrollTo: true, 26 | cancelIcon: { 27 | enabled: true 28 | } 29 | }; 30 | 31 | export const steps: StepOptions[] = [ 32 | { 33 | attachTo: { 34 | element: '.first-element', 35 | on: 'bottom' 36 | }, 37 | buttons: [ 38 | builtInButtons.cancel, 39 | builtInButtons.next 40 | ], 41 | classes: 'custom-class-name-1 custom-class-name-2', 42 | id: 'intro', 43 | title: 'Welcome to Angular Shepherd!', 44 | text: ` 45 |

46 | Angular Shepherd is a JavaScript library for guiding users through your Angular app. 47 | It is an Angular library that wraps Shepherd 48 | and extends its functionality. Shepherd uses Popper.js, 49 | another open source library, to position all of its steps and enable entrance and exit animations. 50 |

51 | 52 |

53 | Popper makes sure your steps never end up off screen or cropped by an 54 | overflow. Try resizing your browser to see what we mean. 55 |

` 56 | }, 57 | { 58 | attachTo: { 59 | element: '.install-element', 60 | on: 'bottom' 61 | }, 62 | buttons: [ 63 | builtInButtons.cancel, 64 | builtInButtons.back, 65 | builtInButtons.next 66 | ], 67 | classes: 'custom-class-name-1 custom-class-name-2', 68 | id: 'installation', 69 | title: 'Installation', 70 | text: 'Installation is simple, if you are using Ember-CLI, just install like any other addon.' 71 | }, 72 | { 73 | attachTo: { 74 | element: '.usage-element', 75 | on: 'bottom' 76 | }, 77 | buttons: [ 78 | builtInButtons.cancel, 79 | builtInButtons.back, 80 | builtInButtons.next 81 | ], 82 | classes: 'custom-class-name-1 custom-class-name-2', 83 | id: 'usage', 84 | title: 'Usage', 85 | text: 'To use the tour service, simply inject it into your application and use it like this example.' 86 | }, 87 | { 88 | attachTo: { 89 | element: '.modal-element', 90 | on: 'top' 91 | }, 92 | buttons: [ 93 | builtInButtons.cancel, 94 | builtInButtons.back, 95 | builtInButtons.next 96 | ], 97 | classes: 'custom-class-name-1 custom-class-name-2', 98 | id: 'modal', 99 | text: ` 100 |

101 | We implemented true modal functionality by disabling clicking of the rest of the page. 102 |

103 | 104 |

105 | If you would like to enable modal, simply do this.get('tour').set('modal', true). 106 |

` 107 | }, 108 | { 109 | attachTo: { 110 | element: '.built-in-buttons-element', 111 | on: 'top' 112 | }, 113 | buttons: [ 114 | builtInButtons.cancel, 115 | builtInButtons.back, 116 | builtInButtons.next 117 | ], 118 | classes: 'custom-class-name-1 custom-class-name-2', 119 | id: 'buttons', 120 | text: `For the common button types ("next", "back", "cancel", etc.), we implemented Ember actions 121 | that perform these actions on your tour automatically. To use them, simply include 122 | in the buttons array in each step.` 123 | }, 124 | { 125 | buttons: [ 126 | builtInButtons.cancel, 127 | builtInButtons.back 128 | ], 129 | id: 'noAttachTo', 130 | title: 'Centered Modals', 131 | classes: 'custom-class-name-1 custom-class-name-2', 132 | text: 'If no attachTo is specified, the modal will appear in the center of the screen, as per the Shepherd docs.' 133 | } 134 | ]; 135 | -------------------------------------------------------------------------------- /projects/shepherd-tester/src/app/shepherd/shepherd.component.css: -------------------------------------------------------------------------------- 1 | .page { 2 | display: flex; 3 | justify-content: center; 4 | } 5 | 6 | .container { 7 | max-width: 1200px; 8 | width: 100%; 9 | } 10 | -------------------------------------------------------------------------------- /projects/shepherd-tester/src/app/shepherd/shepherd.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

angular-shepherd

4 | 5 |

Guide your users through a tour of your app.

6 | 7 |
Installation
8 | 9 |
10 | Install instructions go here 11 |
12 | 13 |
Usage
14 | 15 |
16 | Usage instructions go here 17 |
18 | 19 | 85 |
86 |
87 |
88 | -------------------------------------------------------------------------------- /projects/shepherd-tester/src/app/shepherd/shepherd.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { waitForAsync, ComponentFixture, TestBed } from "@angular/core/testing"; 2 | 3 | import { ShepherdComponent } from "./shepherd.component"; 4 | 5 | describe("ShepherdComponent", () => { 6 | let component: ShepherdComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [ShepherdComponent], 12 | }).compileComponents(); 13 | })); 14 | 15 | beforeEach(() => { 16 | fixture = TestBed.createComponent(ShepherdComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | }); 20 | 21 | it("should create", () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /projects/shepherd-tester/src/app/shepherd/shepherd.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, type AfterViewInit } from '@angular/core'; 2 | import { ShepherdService } from '../../../../shepherd/src/lib/shepherd.service'; 3 | import { steps as defaultSteps, defaultStepOptions } from '../data'; 4 | 5 | @Component({ 6 | selector: 'app-shepherd', 7 | templateUrl: './shepherd.component.html', 8 | styleUrls: ['./shepherd.component.css'], 9 | standalone: true 10 | }) 11 | export class ShepherdComponent implements AfterViewInit { 12 | constructor(private shepherdService: ShepherdService) {} 13 | 14 | ngAfterViewInit() { 15 | this.shepherdService.defaultStepOptions = defaultStepOptions; 16 | this.shepherdService.modal = true; 17 | this.shepherdService.confirmCancel = false; 18 | this.shepherdService.addSteps(defaultSteps); 19 | this.shepherdService.start(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /projects/shepherd-tester/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shipshapecode/angular-shepherd/d850120a9b66b907039c4d70f3bac365f668e2fb/projects/shepherd-tester/src/assets/.gitkeep -------------------------------------------------------------------------------- /projects/shepherd-tester/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /projects/shepherd-tester/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /projects/shepherd-tester/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shipshapecode/angular-shepherd/d850120a9b66b907039c4d70f3bac365f668e2fb/projects/shepherd-tester/src/favicon.ico -------------------------------------------------------------------------------- /projects/shepherd-tester/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ShepherdTester 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /projects/shepherd-tester/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /projects/shepherd-tester/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /projects/shepherd-tester/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "**/*.spec.ts" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /projects/shepherd-tester/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "include": [ 11 | "**/*.spec.ts", 12 | "**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /projects/shepherd/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "ignorePatterns": [ 4 | "!**/*" 5 | ], 6 | "overrides": [ 7 | { 8 | "files": [ 9 | "*.ts" 10 | ], 11 | "parserOptions": { 12 | "project": [ 13 | "projects/shepherd/tsconfig.(lib|spec).json" 14 | ] 15 | }, 16 | "rules": { 17 | "@angular-eslint/directive-selector": [ 18 | "error", 19 | { 20 | "type": "attribute", 21 | "prefix": "lib", 22 | "style": "camelCase" 23 | } 24 | ], 25 | "@angular-eslint/component-selector": [ 26 | "error", 27 | { 28 | "type": "element", 29 | "prefix": "lib", 30 | "style": "kebab-case" 31 | } 32 | ] 33 | } 34 | }, 35 | { 36 | "files": [ 37 | "*.html" 38 | ], 39 | "rules": {} 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /projects/shepherd/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /projects/shepherd/README.md: -------------------------------------------------------------------------------- 1 | # angular-shepherd 2 | 3 | Ship Shape 4 | 5 | **[angular-shepherd is built and maintained by Ship Shape. Contact us for web app consulting, development, and training for your project](https://shipshape.io/)**. 6 | 7 | [![npm version](https://badge.fury.io/js/angular-shepherd.svg)](http://badge.fury.io/js/angular-shepherd) ![Download count all time](https://img.shields.io/npm/dt/angular-shepherd.svg) [![npm](https://img.shields.io/npm/dm/angular-shepherd.svg)]() ![CI Build](https://github.com/shipshapecode/angular-shepherd/workflows/CI%20Build/badge.svg) 8 | 9 | This is an Angular wrapper for the [Shepherd](https://github.com/shipshapecode/shepherd), site tour, library. It provides additional functionality, on top of Shepherd, as well. 10 | 11 | ## Compatibility 12 | 13 | - Angular 8: 0.5.0 14 | - Angular 9: 0.6.0 15 | - Angular 10: 0.7.0 16 | - Angular 11: 11.x 17 | - Angular 12: 12.x 18 | - Angular 13: 13.x 19 | - Angular 14: 14.x 20 | - Angular 15: 15.x 21 | - Angular 16: 16.x 22 | - Angular 17: 17.x 23 | - Angular 18: 18.x 24 | - Angular 19: 19.x 25 | 26 | This has not been tested in anything but Angular 8+. It may or may not work in previous versions or subsequent versions of Angular. We would love to support multiple versions, if people with more Angular knowledge would be willing to help us out! 27 | 28 | ## Installation 29 | 30 | ```bash 31 | npm install angular-shepherd --save 32 | ``` 33 | 34 | ## Usage 35 | 36 | **NOTE: This may not be the proper Angular way to do everything, as I am not an Angular dev, so please let me know if you have suggestions!** 37 | 38 | Shepherd ships a single style file, which you will need to include. You can do so by adding it to your angular.json. 39 | 40 | ```json 41 | "styles": [ 42 | "node_modules/shepherd.js/dist/css/shepherd.css" 43 | ], 44 | ``` 45 | 46 | Then, you will need to inject the `ShepherdService` to be able to interact with Shepherd and call `addSteps` to add your steps, `start` to start the tour, etc. 47 | 48 | You could either do this at the application level, in your application component or on a per component or per route/view basis. 49 | 50 | **NOTE: It is highly recommended to inject ShepherdService into your `app.component.ts`. Injecting it at the app level ensures you only create one instance of Shepherd.** 51 | 52 | In that component you will want to use `AfterViewInit` to `addSteps` to the Shepherd service. 53 | 54 | ```typescript 55 | import { Component, type AfterViewInit } from '@angular/core'; 56 | import { ShepherdService } from 'angular-shepherd'; 57 | import { steps as defaultSteps, defaultStepOptions } from '../data'; 58 | 59 | @Component({ 60 | selector: 'shepherd', 61 | templateUrl: './shepherd.component.html', 62 | styleUrls: ['./shepherd.component.css'] 63 | }) 64 | export class ShepherdComponent implements AfterViewInit { 65 | constructor(private shepherdService: ShepherdService) {} 66 | 67 | ngAfterViewInit() { 68 | this.shepherdService.defaultStepOptions = defaultStepOptions; 69 | this.shepherdService.modal = true; 70 | this.shepherdService.confirmCancel = false; 71 | this.shepherdService.addSteps(defaultSteps); 72 | this.shepherdService.start(); 73 | } 74 | } 75 | ``` 76 | 77 | ## Configuration 78 | 79 | The following configuration options can be set on the ShepherdService to control the way that Shepherd is used. **The only required option is `steps`, which is set via `addSteps`.** 80 | 81 | ### confirmCancel 82 | 83 | `confirmCancel` is a boolean flag, when set to `true` it will pop up a native browser confirm window on cancel, to ensure you want to cancel. 84 | 85 | ### confirmCancelMessage 86 | 87 | `confirmCancelMessage` is a string to display in the confirm dialog when `confirmCancel` is set to true. 88 | 89 | ### defaultStepOptions 90 | 91 | `defaultStepOptions` is used to set the options that will be applied to each step by default. You can pass in any of the options that you can with Shepherd. 92 | 93 | **⚠️ You must set `defaultStepOptions` _BEFORE_ calling `addSteps` to set the steps.** 94 | 95 | It will be an object of a form something like: 96 | 97 | ```js 98 | this.shepherdService.defaultStepOptions = { 99 | classes: 'custom-class-name-1 custom-class-name-2', 100 | scrollTo: false, 101 | cancelIcon: { 102 | enabled: true 103 | } 104 | }; 105 | ``` 106 | 107 | > **default value:** `{}` 108 | 109 | ### requiredElements 110 | 111 | `requiredElements` is an array of objects that indicate DOM elements that are **REQUIRED** by your tour and must exist and be visible for the tour to start. If any elements are not present, it will keep the tour from starting. 112 | 113 | You can also specify a message, which will tell the user what they need to do to make the tour work. 114 | 115 | **⚠️ You must set `requiredElements` _BEFORE_ calling `addSteps` to set the steps.** 116 | 117 | _Example_ 118 | 119 | ```js 120 | this.shepherdService.requiredElements = [ 121 | { 122 | selector: '.search-result-element', 123 | message: 'No search results found. Please execute another search, and try to start the tour again.', 124 | title: 'No results' 125 | }, 126 | { 127 | selector: '.username-element', 128 | message: 'User not logged in, please log in to start this tour.', 129 | title: 'Please login' 130 | } 131 | ]; 132 | ``` 133 | 134 | > **default value:** `[]` 135 | 136 | ### modal 137 | 138 | `modal` is a boolean, that should be set to true, if you would like the rest of the screen, other than the current element, greyed out, and the current element highlighted. If you do not need modal functionality, you can remove this option or set it to false. 139 | 140 | > **default value:** `false` 141 | 142 | ### addSteps 143 | 144 | You must pass an array of steps to `addSteps`, something like this: 145 | 146 | ```js 147 | this.shepherdService.addSteps([ 148 | { 149 | id: 'intro', 150 | attachTo: { 151 | element: '.first-element', 152 | on: 'bottom' 153 | }, 154 | beforeShowPromise: function() { 155 | return new Promise(function(resolve) { 156 | setTimeout(function() { 157 | window.scrollTo(0, 0); 158 | resolve(); 159 | }, 500); 160 | }); 161 | }, 162 | buttons: [ 163 | { 164 | classes: 'shepherd-button-secondary', 165 | text: 'Exit', 166 | type: 'cancel' 167 | }, 168 | { 169 | classes: 'shepherd-button-primary', 170 | text: 'Back', 171 | type: 'back' 172 | }, 173 | { 174 | classes: 'shepherd-button-primary', 175 | text: 'Next', 176 | type: 'next' 177 | } 178 | ], 179 | cancelIcon: { 180 | enabled: true 181 | }, 182 | classes: 'custom-class-name-1 custom-class-name-2', 183 | highlightClass: 'highlight', 184 | scrollTo: false, 185 | title: 'Welcome to Angular-Shepherd!', 186 | text: ['Angular-Shepherd is a JavaScript library for guiding users through your Angular app.'], 187 | when: { 188 | show: () => { 189 | console.log('show step'); 190 | }, 191 | hide: () => { 192 | console.log('hide step'); 193 | } 194 | } 195 | }, 196 | ... 197 | ]); 198 | ``` 199 | 200 | ## Buttons 201 | 202 | In Shepherd, you can have as many buttons as you want inside a step. You can build an object with some premade buttons, making it easier to manipulate and insert in new steps. Buttons by default accept three different types: back, cancel, next. In this simple example, we have three buttons: each one with different types and classes. 203 | 204 | ```js 205 | const builtInButtons = { 206 | cancel: { 207 | classes: 'cancel-button', 208 | text: 'Cancel', 209 | type: 'cancel' 210 | }, 211 | next: { 212 | classes: 'next-button', 213 | text: 'Next', 214 | type: 'next' 215 | }, 216 | back: { 217 | classes: 'back-button', 218 | secondary: true, 219 | text: 'Back', 220 | type: 'back' 221 | } 222 | }; 223 | ``` 224 | 225 | Buttons have an action property, which must be a function. Whenever the button is clicked, the function will be executed. You can use it for default shepherd functions, like `this.shepherdService.complete()` or `this.shepherdService.next()`, or create your own function to use for the action. 226 | 227 | ```js 228 | const builtInButtons = { 229 | complete: { 230 | classes: 'complete-button', 231 | text: 'Finish Tutorial', 232 | action: function () { 233 | return console.log('button clicked'); 234 | } 235 | } 236 | }; 237 | ``` 238 | 239 | **⚠️ You can't set up a type and an action at the same time inside a button**. 240 | 241 | To learn more about button properties, look at the [documentation](https://docs.shepherdjs.dev/api/step/interfaces/stepoptionsbutton/). 242 | 243 | ## Step Options 244 | 245 | See the [Step docs](https://docs.shepherdjs.dev/api/step/classes/step/) for all available Step options. 246 | -------------------------------------------------------------------------------- /projects/shepherd/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 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../../coverage/shepherd'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /projects/shepherd/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/angular-shepherd", 4 | "lib": { 5 | "entryFile": "src/public_api.ts" 6 | }, 7 | "allowedNonPeerDependencies": [ 8 | "disable-scroll", 9 | "shepherd.js" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /projects/shepherd/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-shepherd", 3 | "version": "19.0.2", 4 | "description": "An Angular wrapper for the site tour library Shepherd.", 5 | "keywords": [ 6 | "angular", 7 | "shepherd", 8 | "site tour", 9 | "tour" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/shipshapecode/angular-shepherd.git" 14 | }, 15 | "homepage": "https://github.com/shipshapecode/angular-shepherd", 16 | "license": "MIT", 17 | "author": { 18 | "name": "Robert Wagner", 19 | "email": "rwwagner90@gmail.com", 20 | "url": "https://github.com/rwwagner90" 21 | }, 22 | "dependencies": { 23 | "shepherd.js": "^14.4.0" 24 | }, 25 | "peerDependencies": { 26 | "@angular/common": "^19.0.0", 27 | "@angular/core": "^19.0.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /projects/shepherd/src/lib/shepherd.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ShepherdService } from './shepherd.service'; 4 | 5 | export const builtInButtons = { 6 | cancel: { 7 | classes: 'cancel-button', 8 | secondary: true, 9 | text: 'Exit', 10 | type: 'cancel' 11 | }, 12 | next: { 13 | classes: 'next-button', 14 | text: 'Next', 15 | type: 'next' 16 | }, 17 | back: { 18 | classes: 'back-button', 19 | text: 'Back', 20 | type: 'back' 21 | } 22 | }; 23 | 24 | const steps = [ 25 | { 26 | id: 'intro', 27 | options: { 28 | attachTo: { 29 | element: '.test-element', 30 | on: 'bottom' 31 | }, 32 | buttons: [builtInButtons.cancel, builtInButtons.next], 33 | classes: 'custom-class-name-1 custom-class-name-2', 34 | title: 'Welcome to Ember-Shepherd!', 35 | text: 'Test text', 36 | scrollTo: true, 37 | scrollToHandler() { 38 | return 'custom scrollToHandler'; 39 | } 40 | } 41 | } 42 | ]; 43 | 44 | describe('ShepherdService', () => { 45 | beforeEach(() => TestBed.configureTestingModule({})); 46 | 47 | it('starts the tour when the `start` event is triggered', () => { 48 | const service: ShepherdService = TestBed.inject(ShepherdService); 49 | const mockTourObject = { 50 | start() { 51 | expect(true).toBeTruthy('The tour was started'); 52 | } 53 | }; 54 | 55 | service.addSteps(steps); 56 | 57 | // @ts-ignore: Tests do not need all methods 58 | service.tourObject = mockTourObject; 59 | 60 | service.start(); 61 | }); 62 | 63 | it('passes through a custom scrollToHandler option', () => { 64 | const service: ShepherdService = TestBed.inject(ShepherdService); 65 | const mockTourObject = { 66 | start() { 67 | expect(steps[0]?.options.scrollToHandler()).toBe( 68 | 'custom scrollToHandler', 69 | 'The handler was passed through as an option on the step' 70 | ); 71 | } 72 | }; 73 | 74 | service.addSteps(steps); 75 | 76 | // @ts-ignore: Tests do not need all methods 77 | service.tourObject = mockTourObject; 78 | 79 | service.start(); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /projects/shepherd/src/lib/shepherd.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import Shepherd, { 3 | type TourOptions, 4 | type StepOptions, 5 | type Tour 6 | } from 'shepherd.js'; 7 | import { elementIsHidden } from './utils/dom'; 8 | import { makeButton } from './utils/buttons'; 9 | 10 | interface RequiredElement { 11 | message: string; 12 | selector: string; 13 | title: string; 14 | } 15 | @Injectable({ 16 | providedIn: 'root' 17 | }) 18 | export class ShepherdService { 19 | confirmCancel: TourOptions['confirmCancel'] = false; 20 | confirmCancelMessage?: TourOptions['confirmCancelMessage']; 21 | defaultStepOptions: StepOptions = {}; 22 | errorTitle?: string; 23 | exitOnEsc: TourOptions['exitOnEsc'] = true; 24 | isActive = false; 25 | keyboardNavigation: TourOptions['keyboardNavigation'] = true; 26 | messageForUser: string | null = null; 27 | modal = false; 28 | requiredElements: Array = []; 29 | tourName: TourOptions['tourName'] = undefined; 30 | tourObject: Tour | null = null; 31 | 32 | constructor() {} 33 | 34 | /** 35 | * Get the tour object and call back 36 | */ 37 | back() { 38 | this.tourObject?.back(); 39 | } 40 | 41 | /** 42 | * Cancel the tour 43 | */ 44 | cancel() { 45 | this.tourObject?.cancel(); 46 | } 47 | 48 | /** 49 | * Complete the tour 50 | */ 51 | complete() { 52 | this.tourObject?.complete(); 53 | } 54 | 55 | /** 56 | * Hides the current step 57 | */ 58 | hide() { 59 | this.tourObject?.hide(); 60 | } 61 | 62 | /** 63 | * Advance the tour to the next step 64 | */ 65 | next() { 66 | this.tourObject?.next(); 67 | } 68 | 69 | /** 70 | * Show a specific step, by passing its id 71 | * @param id The id of the step you want to show 72 | */ 73 | show(id: string | number) { 74 | this.tourObject?.show(id); 75 | } 76 | 77 | /** 78 | * Start the tour 79 | */ 80 | start() { 81 | this.isActive = true; 82 | this.tourObject?.start(); 83 | } 84 | 85 | /** 86 | * This function is called when a tour is completed or cancelled to initiate cleanup. 87 | * @param completeOrCancel 'complete' or 'cancel' 88 | */ 89 | onTourFinish(completeOrCancel: string) { 90 | this.isActive = false; 91 | } 92 | 93 | /** 94 | * Take a set of steps and create a tour object based on the current configuration 95 | * @param steps An array of steps 96 | */ 97 | addSteps(steps: Array) { 98 | this._initialize(); 99 | const tour = this.tourObject; 100 | 101 | // Return nothing if there are no steps or if somehow there is no tour. 102 | if (!tour || !steps || !Array.isArray(steps) || steps.length === 0) { 103 | return; 104 | } 105 | 106 | if (!this.requiredElementsPresent()) { 107 | tour.addStep({ 108 | buttons: [ 109 | { 110 | text: 'Exit', 111 | action: tour.cancel 112 | } 113 | ], 114 | id: 'error', 115 | title: this.errorTitle, 116 | text: [this.messageForUser as string] 117 | }); 118 | return; 119 | } 120 | 121 | steps.forEach((step) => { 122 | if (step.buttons) { 123 | step.buttons = step.buttons.map(makeButton.bind(this), this); 124 | } 125 | 126 | tour.addStep(step); 127 | }); 128 | } 129 | 130 | /** 131 | * Observes the array of requiredElements, which are the elements that must be present at the start of the tour, 132 | * and determines if they exist, and are visible, if either is false, it will stop the tour from executing. 133 | */ 134 | private requiredElementsPresent() { 135 | let allElementsPresent = true; 136 | 137 | /* istanbul ignore next: also can't test this due to things attached to root blowing up tests */ 138 | this.requiredElements.forEach((element) => { 139 | const selectedElement = document.querySelector(element.selector); 140 | 141 | if ( 142 | allElementsPresent && 143 | (!selectedElement || elementIsHidden(selectedElement as HTMLElement)) 144 | ) { 145 | allElementsPresent = false; 146 | this.errorTitle = element.title; 147 | this.messageForUser = element.message; 148 | } 149 | }); 150 | 151 | return allElementsPresent; 152 | } 153 | 154 | /** 155 | * Initializes the tour, creates a new Shepherd.Tour. sets options, and binds events 156 | */ 157 | private _initialize() { 158 | const tourObject = new Shepherd.Tour({ 159 | confirmCancel: this.confirmCancel, 160 | confirmCancelMessage: this.confirmCancelMessage, 161 | defaultStepOptions: this.defaultStepOptions, 162 | keyboardNavigation: this.keyboardNavigation, 163 | tourName: this.tourName, 164 | useModalOverlay: this.modal, 165 | exitOnEsc: this.exitOnEsc 166 | }); 167 | 168 | tourObject.on('complete', this.onTourFinish.bind(this, 'complete')); 169 | tourObject.on('cancel', this.onTourFinish.bind(this, 'cancel')); 170 | 171 | this.tourObject = tourObject; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /projects/shepherd/src/lib/utils/buttons.ts: -------------------------------------------------------------------------------- 1 | import type { StepOptionsButton } from 'shepherd.js'; 2 | import type { ShepherdService } from '../shepherd.service'; 3 | 4 | export type AngularShepherdButton = StepOptionsButton & { 5 | type?: 'back' | 'cancel' | 'next'; 6 | }; 7 | 8 | /** 9 | * Creates a button of the specified type, with the given classes and text 10 | * 11 | * @param button.type The type of button cancel, back, or next 12 | * @param button.classes Classes to apply to the button 13 | * @param button.text The text for the button 14 | * @param button.action The action to call 15 | */ 16 | export function makeButton( 17 | this: ShepherdService, 18 | button: AngularShepherdButton 19 | ) { 20 | const { classes, disabled, label, secondary, type, text } = button; 21 | const builtInButtonTypes = ['back', 'cancel', 'next']; 22 | 23 | if (!type) { 24 | return button; 25 | } 26 | 27 | if (builtInButtonTypes.indexOf(type) === -1) { 28 | throw new Error( 29 | `'type' property must be one of 'back', 'cancel', or 'next'` 30 | ); 31 | } 32 | 33 | return { 34 | action: this[type].bind(this), 35 | classes, 36 | disabled, 37 | label, 38 | secondary, 39 | text 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /projects/shepherd/src/lib/utils/dom.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper method to check if element is hidden, since we cannot use :visible without jQuery 3 | * @param element The element to check for visibility 4 | * @returns true if element is hidden 5 | */ 6 | export function elementIsHidden(element: HTMLElement): boolean { 7 | return element.offsetWidth === 0 && element.offsetHeight === 0; 8 | } 9 | -------------------------------------------------------------------------------- /projects/shepherd/src/public_api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of shepherd 3 | */ 4 | 5 | export * from './lib/shepherd.service'; 6 | -------------------------------------------------------------------------------- /projects/shepherd/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "outDir": "../../out-tsc/lib", 8 | "sourceMap": true, 9 | "inlineSources": true, 10 | "types": [] 11 | }, 12 | "angularCompilerOptions": { 13 | "annotateForClosureCompiler": true, 14 | "compilationMode": "partial", 15 | "skipTemplateCodegen": true, 16 | "strictMetadataEmit": true, 17 | "fullTemplateTypeCheck": true, 18 | "strictInjectionParameters": true, 19 | "enableResourceInlining": true 20 | }, 21 | "exclude": ["**/*.spec.ts"] 22 | } 23 | -------------------------------------------------------------------------------- /projects/shepherd/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "include": [ 11 | "**/*.spec.ts", 12 | "**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "allowArbitraryExtensions": true, 5 | "allowSyntheticDefaultImports": false, 6 | "baseUrl": "./", 7 | "declaration": false, 8 | "emitDecoratorMetadata": true, 9 | "esModuleInterop": true, 10 | "experimentalDecorators": true, 11 | "importHelpers": true, 12 | "lib": ["es2018", "dom"], 13 | "module": "ESNext", 14 | "moduleResolution": "bundler", 15 | "outDir": "./dist/out-tsc", 16 | "target": "ES2022", 17 | "typeRoots": ["node_modules/@types"], 18 | "useDefineForClassFields": false, 19 | // We don't want to include types dependencies in our compiled output, so tell TypeScript 20 | // to enforce using `import type` instead of `import` for Types. 21 | "verbatimModuleSyntax": true, 22 | "strict": true, 23 | } 24 | } 25 | --------------------------------------------------------------------------------