├── .gitignore ├── .prettierrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── angular.json ├── docs ├── 3rdpartylicenses.txt ├── favicon.ico ├── index.html ├── main.js ├── polyfills.js ├── runtime.js └── styles.css ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.e2e.json ├── package-lock.json ├── package.json ├── projects └── ngx-guided-tour │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ ├── lib │ │ ├── guided-tour-base-theme.scss │ │ ├── guided-tour.component.scss │ │ ├── guided-tour.component.ts │ │ ├── guided-tour.constants.ts │ │ ├── guided-tour.module.ts │ │ ├── guided-tour.service.ts │ │ ├── ngx-guided-tour.module.ts │ │ └── windowref.service.ts │ ├── public_api.ts │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json ├── src ├── app │ ├── app.component.css │ ├── app.component.html │ ├── app.component.spec.ts │ ├── app.component.ts │ └── app.module.ts ├── assets │ └── .gitkeep ├── browserslist ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── karma.conf.js ├── main.ts ├── polyfills.ts ├── styles.css ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json └── tslint.json ├── tsconfig.json └── tslint.json /.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 | node_modules 11 | 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | 42 | .angular -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "jsxBracketSameLine": false, 3 | "singleQuote": true, 4 | "jsxSingleQuote": true, 5 | "trailingComma": "none", 6 | "printWidth": 100, 7 | "tabWidth": 4, 8 | "useTabs": false, 9 | "arrowParens": "always", 10 | "overrides": [ 11 | { 12 | "files": "*.yaml", 13 | "options": { "tabWidth": 4 } 14 | }, 15 | { 16 | "files": "*.yml", 17 | "options": { "tabWidth": 4 } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all of your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are in the proper dependency object in the `package.json` file. 11 | 2. Update the README.md with any potential changes to the interface. 12 | 3. Increase the version numbers in `package.json` and the README.md to the new version that this Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 13 | 4. If any unit tests exist, run the test suite to ensure they are still passing. Add unit tests for your added logic as appropriate. 14 | 5. You may merge the Pull Request in once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you. 15 | 16 | ## Style Guide 17 | 18 | Please conform to our `tslint` and `.stylelintrc` files in order to maintain a consistent coding style. We recommend the `stylelint` VS Code extension for SASS linting. 19 | 20 | ## How to Build 21 | 22 | ### Module 23 | To build the core module, simply run `npm run build` in the root of this repository. This will build the module locally to the `build` folder. The contents of `build/package` are what eventually gets published to the npm registry. 24 | 25 | ### Demo application 26 | To see the demo application, switch to the `gh-pages` branch. You can run the application at `http://localhost:4200/` by running `npm start`. Simply replace the appropriate module inside `node_modules` with your built module in order to test your modifications prior to submitting your PR. 27 | 28 | ## Code of Conduct 29 | 30 | ### Our Pledge 31 | 32 | In the interest of fostering an open and welcoming environment, we as 33 | contributors and maintainers pledge to making participation in our project and 34 | our community a harassment-free experience for everyone, regardless of age, body 35 | size, disability, ethnicity, gender identity and expression, level of experience, 36 | nationality, personal appearance, race, religion, or sexual identity and 37 | orientation. 38 | 39 | ### Our Standards 40 | 41 | Examples of behavior that contributes to creating a positive environment 42 | include: 43 | 44 | * Using welcoming and inclusive language 45 | * Being respectful of differing viewpoints and experiences 46 | * Gracefully accepting constructive criticism 47 | * Focusing on what is best for the community 48 | * Showing empathy towards other community members 49 | 50 | Examples of unacceptable behavior by participants include: 51 | 52 | * The use of sexualized language or imagery and unwelcome sexual attention or 53 | advances 54 | * Trolling, insulting/derogatory comments, and personal or political attacks 55 | * Public or private harassment 56 | * Publishing others' private information, such as a physical or electronic 57 | address, without explicit permission 58 | * Other conduct which could reasonably be considered inappropriate in a 59 | professional setting 60 | 61 | ### Our Responsibilities 62 | 63 | Project maintainers are responsible for clarifying the standards of acceptable 64 | behavior and are expected to take appropriate and fair corrective action in 65 | response to any instances of unacceptable behavior. 66 | 67 | Project maintainers have the right and responsibility to remove, edit, or 68 | reject comments, commits, code, wiki edits, issues, and other contributions 69 | that are not aligned to this Code of Conduct, or to ban temporarily or 70 | permanently any contributor for other behaviors that they deem inappropriate, 71 | threatening, offensive, or harmful. 72 | 73 | ### Scope 74 | 75 | This Code of Conduct applies both within project spaces and in public spaces 76 | when an individual is representing the project or its community. Examples of 77 | representing a project or community include using an official project e-mail 78 | address, posting via an official social media account, or acting as an appointed 79 | representative at an online or offline event. Representation of a project may be 80 | further defined and clarified by project maintainers. 81 | 82 | ### Enforcement 83 | 84 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 85 | reported by contacting the project team at developers@lsq.com. All 86 | complaints will be reviewed and investigated and will result in a response that 87 | is deemed necessary and appropriate to the circumstances. The project team is 88 | obligated to maintain confidentiality with regard to the reporter of an incident. 89 | Further details of specific enforcement policies may be posted separately. 90 | 91 | Project maintainers who do not follow or enforce the Code of Conduct in good 92 | faith may face temporary or permanent repercussions as determined by other 93 | members of the project's leadership. 94 | 95 | ### Attribution 96 | 97 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 98 | available at [http://contributor-covenant.org/version/1/4][version] 99 | 100 | [homepage]: http://contributor-covenant.org 101 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 LSQ 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 | # ngx-guided-tour 2 | Guided tour component using SASS and typescript. Allows you to use selectors to step your user through workflows and introduce them to your application. Customiziable theme and many features. Heavily inspired by [react-joyride](https://github.com/gilbarbara/react-joyride) 3 | 4 | See a quick demo - [https://lsqlabs.github.io/ngx-guided-tour/](https://lsqlabs.github.io/ngx-guided-tour/) 5 | 6 | ## Installation 7 | 8 | 1. Install npm module: 9 | 10 | `npm install ngx-guided-tour --save` 11 | 12 | 2. Add modules to app.module.ts 13 | ```typescript 14 | import {GuidedTourModule, GuidedTourService} from 'ngx-guided-tour'; 15 | 16 | @NgModule({ 17 | imports: [ 18 | GuidedTourModule, 19 | ... 20 | ], 21 | providers: [ 22 | GuidedTourService, 23 | ... 24 | ], 25 | ``` 26 | 27 | ## Usage 28 | 29 | Add ngx-guided-tour to your app.component.html . 30 | 31 | ```html 32 | 33 | ``` 34 | 35 | Add guided-tour-base-theme.scss to your main style import page. If you want to create your own theme add it after your defined constants. 36 | 37 | ```scss 38 | @import '../node_modules/ngx-guided-tour/scss/guided-tour-base-theme.scss'; 39 | ``` 40 | Define your tour using the GuidedTour type: 41 | 42 | ```typescript 43 | interface GuidedTour { 44 | /** Identifier for tour */ 45 | tourId: string; 46 | /** Use orb to start tour */ 47 | useOrb?: boolean; 48 | /** Steps for the tour */ 49 | steps: TourStep[]; 50 | /** Function will be called when tour is skipped */ 51 | skipCallback?: (stepSkippedOn: number) => void; 52 | /** Function will be called when tour is completed */ 53 | completeCallback?: () => void; 54 | /** Minimum size of screen in pixels before the tour is run, if the tour is resized below this value the user will be told to resize */ 55 | minimumScreenSize?: number; 56 | /** Dialog shown if the window width is smaller than the defined minimum screen size. */ 57 | resizeDialog?: { 58 | /** Resize dialog title text */ 59 | title?: string; 60 | /** Resize dialog text */ 61 | content: string; 62 | } 63 | } 64 | ``` 65 | 66 | and steps: 67 | ```typescript 68 | interface TourStep { 69 | /** Selector for element that will be highlighted */ 70 | selector?: string; 71 | /** Tour title text */ 72 | title?: string; 73 | /** Tour step text */ 74 | content: string; 75 | /** Where the tour step will appear next to the selected element */ 76 | orientation?: Orientation | OrientationConfiguration[]; 77 | /** Action that happens when the step is opened */ 78 | action?: () => void; 79 | /** Action that happens when the step is closed */ 80 | closeAction?: () => void; 81 | /** Skips this step, this is so you do not have create multiple tour configurations based on user settings/permissions */ 82 | skipStep?: boolean; 83 | /** Adds some padding for things like sticky headers when scrolling to an element */ 84 | scrollAdjustment?: number; 85 | /** Adds default padding around tour highlighting. Does not need to be true for highlightPadding to work */ 86 | useHighlightPadding?: boolean; 87 | /** Adds padding around tour highlighting in pixels, this overwrites the default for this step. Is not dependent on useHighlightPadding being true */ 88 | highlightPadding?: number; 89 | } 90 | ``` 91 | 92 | Orientation configuration: 93 | ```typescript 94 | interface OrientationConfiguration { 95 | /** Where the tour step will appear next to the selected element */ 96 | orientationDirection: Orientation, 97 | /** When this orientation configuration starts in pixels */ 98 | maximumSize?: number 99 | } 100 | ``` 101 | 102 | Then use the `GuidedTourService` to to start your tour by calling `GuidedTourService.startTour`. 103 | 104 | If a selector is not found, the step will be skipped. 105 | 106 | ## Publishing Updates to npm 107 | 1. Make sure you are logged in via cli to the npm account that owns the package (if you work at LSQ Funding, check the 1Password vault). 108 | 2. Increment the version number in [projects/ngx-guided-tour/package.json](https://github.com/lsqlabs/ngx-guided-tour/blob/master/projects/ngx-guided-tour/package.json) appropriately. 109 | 2. `npm run build:lib` 110 | 3. `npm run publish` 111 | ## TourStep Interface 112 | 113 | selector (optional) - If no selector is present then the tour will show a step in the middle of the page. If a selector is set but not found, it will skip the step. 114 | 115 | title (optional) - Title that shows on the top of the step. 116 | 117 | content - Content of the tourstep. Uses inner html so tags will work. 118 | 119 | orientation (optional) - Defaults to top. Accepts bottom, bottomLeft, bottomRight, center, left, right, top, topLeft, and topRight. Can be taken from the guided-tour.constants.ts file. This also supports a array of OrientationConfiguration. When an array of OrientationConfiguration is passed to it, it will use the smallest maximumSize the screen can fit into. This is useful for tablet or mobile flexing. It will also change when the user resizes the screen. 120 | 121 | action (optional) - Function called at the beginning of step. This is executed before the tour step is rendered allowing for content to appear. 122 | 123 | closeAction (optional) - Function called after step is ended. 124 | 125 | scrollAdjustment (optional) - Number used to adjust where to scroll to on a step and when to scroll. 126 | 127 | useHighlightPadding (optional) - Adds some extra padding around the highlight for elements that may need just a little more on the highlight. 128 | 129 | ## GuidedTour Interface 130 | 131 | tourId - unique Identifer string 132 | 133 | useOrb (optional) - Use orb to start tour. The tour will start when the user hovers over the orb. The orb is based on the positioning of the first step. 134 | 135 | steps - List of TourSteps that the tour steps through. 136 | 137 | skipCallback (optional) - Function called when the tour is skipped. Passes the index of the step that was skipped on. 138 | 139 | completeCallback (optional) - Function is called when the tour is completed (done is pressed). 140 | 141 | minimumScreenSize (optional) - Will enforce a minimum size before the tour will start (in pixels). If the window is resized below this size during a tour a message will inform the user to expand their browser. 142 | 143 | preventBackdropFromAdvancing (optional) - Prevents the tour from advancing by clicking the backdrop. This should only be set if you are completely sure your tour is displaying correctly on all screen sizes otherwise a user can get stuck. 144 | 145 | ## ngx-guided-tour component inputs 146 | 147 | topOfPageAdjustment (optional) - Used to adjust values to determine scroll. This is a blanket value to adjust for things like nav bars. 148 | 149 | tourStepWidth (optional) - Sets the width of tour steps. 150 | 151 | minimalTourStepWidth (optional) - The minimal width of tour steps. 152 | 153 | skipText (optional) - The text of the skip button. 154 | 155 | nextText (optional) - The text of the next button. 156 | 157 | doneText (optional) - The text of the done button (button on the last step). 158 | 159 | closeText (optional) - The text of the close button (shown on the resize popup). 160 | 161 | backText (optional) - The text of the back button. 162 | 163 | progressIndicatorLocation (optional) - The location of the progress indicator (e.g. "1/5"). It can be placed inside the next button (default), at the top of the tour block or hidden. If set to ProgressIndicatorLocation.TopOfTourBlock the indicator will be shown on all steps. If it's shown inside the next button, it will be hidden on the last step. 164 | 165 | progressIndicator (optional) - A ng-template to customize the progress indicator (e.g. "1/5"). The following context is provided: 166 | - currentStepNumber: The number of the current step (starting with 1) 167 | - totalSteps: The total number of steps 168 | 169 | 170 | ## Style variables 171 | These SASS variables have default values, but they can be set to customize the tour elements. Define them before importing guided-tour-base-theme.scss . 172 | 173 | $tour-skip-link-color : Skip button color. 174 | 175 | $tour-text-color : Color of the text that is in the tour step box. 176 | 177 | $tour-next-text-color : Next button text color. 178 | 179 | $tour-zindex : Base z-index for the tour. 180 | 181 | $tour-orb-color : Color of the orb to start a tour. 182 | 183 | $tour-next-button-color : Next button color. 184 | 185 | $tour-next-button-hover : Next button hover color. 186 | 187 | $tour-back-button-color : Back button color. 188 | 189 | $tour-shadow-color : Shadow backdrop that is used for the tour. 190 | 191 | $tour-step-color : Background color for the tour step box. -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ngx-guided-tour-demo": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": {}, 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "docs", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "src/tsconfig.app.json", 21 | "assets": [ 22 | "src/favicon.ico", 23 | "src/assets" 24 | ], 25 | "styles": [ 26 | "src/styles.css", 27 | "projects/ngx-guided-tour/src/lib/guided-tour-base-theme.scss" 28 | ], 29 | "baseHref": "/ngx-guided-tour/", 30 | "deployUrl": "/ngx-guided-tour/", 31 | "scripts": [] 32 | }, 33 | "configurations": { 34 | "production": { 35 | "fileReplacements": [ 36 | { 37 | "replace": "src/environments/environment.ts", 38 | "with": "src/environments/environment.prod.ts" 39 | } 40 | ], 41 | "optimization": true, 42 | "outputHashing": "all", 43 | "sourceMap": false, 44 | "extractCss": true, 45 | "namedChunks": false, 46 | "aot": true, 47 | "extractLicenses": true, 48 | "vendorChunk": false, 49 | "buildOptimizer": true, 50 | "budgets": [ 51 | { 52 | "type": "initial", 53 | "maximumWarning": "2mb", 54 | "maximumError": "5mb" 55 | } 56 | ] 57 | } 58 | } 59 | }, 60 | "serve": { 61 | "builder": "@angular-devkit/build-angular:dev-server", 62 | "options": { 63 | "browserTarget": "ngx-guided-tour-demo:build" 64 | }, 65 | "configurations": { 66 | "production": { 67 | "browserTarget": "ngx-guided-tour-demo:build:production" 68 | } 69 | } 70 | }, 71 | "extract-i18n": { 72 | "builder": "@angular-devkit/build-angular:extract-i18n", 73 | "options": { 74 | "browserTarget": "ngx-guided-tour-demo:build" 75 | } 76 | }, 77 | "test": { 78 | "builder": "@angular-devkit/build-angular:karma", 79 | "options": { 80 | "main": "src/test.ts", 81 | "polyfills": "src/polyfills.ts", 82 | "tsConfig": "src/tsconfig.spec.json", 83 | "karmaConfig": "src/karma.conf.js", 84 | "styles": [ 85 | "src/styles.css" 86 | ], 87 | "scripts": [], 88 | "assets": [ 89 | "src/favicon.ico", 90 | "src/assets" 91 | ] 92 | } 93 | }, 94 | "lint": { 95 | "builder": "@angular-devkit/build-angular:tslint", 96 | "options": { 97 | "tsConfig": [ 98 | "src/tsconfig.app.json", 99 | "src/tsconfig.spec.json" 100 | ], 101 | "exclude": [ 102 | "**/node_modules/**" 103 | ] 104 | } 105 | } 106 | } 107 | }, 108 | "ngx-guided-tour-demo-e2e": { 109 | "root": "e2e", 110 | "projectType": "application", 111 | "prefix": "", 112 | "architect": { 113 | "e2e": { 114 | "builder": "@angular-devkit/build-angular:protractor", 115 | "options": { 116 | "protractorConfig": "e2e/protractor.conf.js", 117 | "devServerTarget": "ngx-guided-tour-demo:serve" 118 | }, 119 | "configurations": { 120 | "production": { 121 | "devServerTarget": "ngx-guided-tour-demo:serve:production" 122 | } 123 | } 124 | }, 125 | "lint": { 126 | "builder": "@angular-devkit/build-angular:tslint", 127 | "options": { 128 | "tsConfig": "e2e/tsconfig.e2e.json", 129 | "exclude": [ 130 | "**/node_modules/**" 131 | ] 132 | } 133 | } 134 | } 135 | }, 136 | "ngx-guided-tour": { 137 | "root": "projects/ngx-guided-tour", 138 | "sourceRoot": "projects/ngx-guided-tour/src", 139 | "projectType": "library", 140 | "prefix": "lib", 141 | "architect": { 142 | "build": { 143 | "builder": "@angular-devkit/build-ng-packagr:build", 144 | "options": { 145 | "tsConfig": "projects/ngx-guided-tour/tsconfig.lib.json", 146 | "project": "projects/ngx-guided-tour/ng-package.json" 147 | } 148 | }, 149 | "test": { 150 | "builder": "@angular-devkit/build-angular:karma", 151 | "options": { 152 | "main": "projects/ngx-guided-tour/src/test.ts", 153 | "tsConfig": "projects/ngx-guided-tour/tsconfig.spec.json", 154 | "karmaConfig": "projects/ngx-guided-tour/karma.conf.js" 155 | } 156 | }, 157 | "lint": { 158 | "builder": "@angular-devkit/build-angular:tslint", 159 | "options": { 160 | "tsConfig": [ 161 | "projects/ngx-guided-tour/tsconfig.lib.json", 162 | "projects/ngx-guided-tour/tsconfig.spec.json" 163 | ], 164 | "exclude": [ 165 | "**/node_modules/**" 166 | ] 167 | } 168 | } 169 | } 170 | } 171 | }, 172 | "defaultProject": "ngx-guided-tour-demo" 173 | } -------------------------------------------------------------------------------- /docs/3rdpartylicenses.txt: -------------------------------------------------------------------------------- 1 | @angular/common 2 | MIT 3 | 4 | @angular/core 5 | MIT 6 | 7 | @angular/platform-browser 8 | MIT 9 | 10 | lodash 11 | MIT 12 | Copyright OpenJS Foundation and other contributors 13 | 14 | Based on Underscore.js, copyright Jeremy Ashkenas, 15 | DocumentCloud and Investigative Reporters & Editors 16 | 17 | This software consists of voluntary contributions made by many 18 | individuals. For exact contribution history, see the revision history 19 | available at https://github.com/lodash/lodash 20 | 21 | The following license applies to all parts of this software except as 22 | documented below: 23 | 24 | ==== 25 | 26 | Permission is hereby granted, free of charge, to any person obtaining 27 | a copy of this software and associated documentation files (the 28 | "Software"), to deal in the Software without restriction, including 29 | without limitation the rights to use, copy, modify, merge, publish, 30 | distribute, sublicense, and/or sell copies of the Software, and to 31 | permit persons to whom the Software is furnished to do so, subject to 32 | the following conditions: 33 | 34 | The above copyright notice and this permission notice shall be 35 | included in all copies or substantial portions of the Software. 36 | 37 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 38 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 39 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 40 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 41 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 42 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 43 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 44 | 45 | ==== 46 | 47 | Copyright and related rights for sample code are waived via CC0. Sample 48 | code is defined as all source code displayed within the prose of the 49 | documentation. 50 | 51 | CC0: http://creativecommons.org/publicdomain/zero/1.0/ 52 | 53 | ==== 54 | 55 | Files located in the node_modules and vendor directories are externally 56 | maintained libraries used by this software which have their own 57 | licenses; we recommend you read them, as their terms may differ from the 58 | terms above. 59 | 60 | 61 | ngx-guided-tour 62 | MIT 63 | 64 | rxjs 65 | Apache-2.0 66 | Apache License 67 | Version 2.0, January 2004 68 | http://www.apache.org/licenses/ 69 | 70 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 71 | 72 | 1. Definitions. 73 | 74 | "License" shall mean the terms and conditions for use, reproduction, 75 | and distribution as defined by Sections 1 through 9 of this document. 76 | 77 | "Licensor" shall mean the copyright owner or entity authorized by 78 | the copyright owner that is granting the License. 79 | 80 | "Legal Entity" shall mean the union of the acting entity and all 81 | other entities that control, are controlled by, or are under common 82 | control with that entity. For the purposes of this definition, 83 | "control" means (i) the power, direct or indirect, to cause the 84 | direction or management of such entity, whether by contract or 85 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 86 | outstanding shares, or (iii) beneficial ownership of such entity. 87 | 88 | "You" (or "Your") shall mean an individual or Legal Entity 89 | exercising permissions granted by this License. 90 | 91 | "Source" form shall mean the preferred form for making modifications, 92 | including but not limited to software source code, documentation 93 | source, and configuration files. 94 | 95 | "Object" form shall mean any form resulting from mechanical 96 | transformation or translation of a Source form, including but 97 | not limited to compiled object code, generated documentation, 98 | and conversions to other media types. 99 | 100 | "Work" shall mean the work of authorship, whether in Source or 101 | Object form, made available under the License, as indicated by a 102 | copyright notice that is included in or attached to the work 103 | (an example is provided in the Appendix below). 104 | 105 | "Derivative Works" shall mean any work, whether in Source or Object 106 | form, that is based on (or derived from) the Work and for which the 107 | editorial revisions, annotations, elaborations, or other modifications 108 | represent, as a whole, an original work of authorship. For the purposes 109 | of this License, Derivative Works shall not include works that remain 110 | separable from, or merely link (or bind by name) to the interfaces of, 111 | the Work and Derivative Works thereof. 112 | 113 | "Contribution" shall mean any work of authorship, including 114 | the original version of the Work and any modifications or additions 115 | to that Work or Derivative Works thereof, that is intentionally 116 | submitted to Licensor for inclusion in the Work by the copyright owner 117 | or by an individual or Legal Entity authorized to submit on behalf of 118 | the copyright owner. For the purposes of this definition, "submitted" 119 | means any form of electronic, verbal, or written communication sent 120 | to the Licensor or its representatives, including but not limited to 121 | communication on electronic mailing lists, source code control systems, 122 | and issue tracking systems that are managed by, or on behalf of, the 123 | Licensor for the purpose of discussing and improving the Work, but 124 | excluding communication that is conspicuously marked or otherwise 125 | designated in writing by the copyright owner as "Not a Contribution." 126 | 127 | "Contributor" shall mean Licensor and any individual or Legal Entity 128 | on behalf of whom a Contribution has been received by Licensor and 129 | subsequently incorporated within the Work. 130 | 131 | 2. Grant of Copyright License. Subject to the terms and conditions of 132 | this License, each Contributor hereby grants to You a perpetual, 133 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 134 | copyright license to reproduce, prepare Derivative Works of, 135 | publicly display, publicly perform, sublicense, and distribute the 136 | Work and such Derivative Works in Source or Object form. 137 | 138 | 3. Grant of Patent License. Subject to the terms and conditions of 139 | this License, each Contributor hereby grants to You a perpetual, 140 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 141 | (except as stated in this section) patent license to make, have made, 142 | use, offer to sell, sell, import, and otherwise transfer the Work, 143 | where such license applies only to those patent claims licensable 144 | by such Contributor that are necessarily infringed by their 145 | Contribution(s) alone or by combination of their Contribution(s) 146 | with the Work to which such Contribution(s) was submitted. If You 147 | institute patent litigation against any entity (including a 148 | cross-claim or counterclaim in a lawsuit) alleging that the Work 149 | or a Contribution incorporated within the Work constitutes direct 150 | or contributory patent infringement, then any patent licenses 151 | granted to You under this License for that Work shall terminate 152 | as of the date such litigation is filed. 153 | 154 | 4. Redistribution. You may reproduce and distribute copies of the 155 | Work or Derivative Works thereof in any medium, with or without 156 | modifications, and in Source or Object form, provided that You 157 | meet the following conditions: 158 | 159 | (a) You must give any other recipients of the Work or 160 | Derivative Works a copy of this License; and 161 | 162 | (b) You must cause any modified files to carry prominent notices 163 | stating that You changed the files; and 164 | 165 | (c) You must retain, in the Source form of any Derivative Works 166 | that You distribute, all copyright, patent, trademark, and 167 | attribution notices from the Source form of the Work, 168 | excluding those notices that do not pertain to any part of 169 | the Derivative Works; and 170 | 171 | (d) If the Work includes a "NOTICE" text file as part of its 172 | distribution, then any Derivative Works that You distribute must 173 | include a readable copy of the attribution notices contained 174 | within such NOTICE file, excluding those notices that do not 175 | pertain to any part of the Derivative Works, in at least one 176 | of the following places: within a NOTICE text file distributed 177 | as part of the Derivative Works; within the Source form or 178 | documentation, if provided along with the Derivative Works; or, 179 | within a display generated by the Derivative Works, if and 180 | wherever such third-party notices normally appear. The contents 181 | of the NOTICE file are for informational purposes only and 182 | do not modify the License. You may add Your own attribution 183 | notices within Derivative Works that You distribute, alongside 184 | or as an addendum to the NOTICE text from the Work, provided 185 | that such additional attribution notices cannot be construed 186 | as modifying the License. 187 | 188 | You may add Your own copyright statement to Your modifications and 189 | may provide additional or different license terms and conditions 190 | for use, reproduction, or distribution of Your modifications, or 191 | for any such Derivative Works as a whole, provided Your use, 192 | reproduction, and distribution of the Work otherwise complies with 193 | the conditions stated in this License. 194 | 195 | 5. Submission of Contributions. Unless You explicitly state otherwise, 196 | any Contribution intentionally submitted for inclusion in the Work 197 | by You to the Licensor shall be under the terms and conditions of 198 | this License, without any additional terms or conditions. 199 | Notwithstanding the above, nothing herein shall supersede or modify 200 | the terms of any separate license agreement you may have executed 201 | with Licensor regarding such Contributions. 202 | 203 | 6. Trademarks. This License does not grant permission to use the trade 204 | names, trademarks, service marks, or product names of the Licensor, 205 | except as required for reasonable and customary use in describing the 206 | origin of the Work and reproducing the content of the NOTICE file. 207 | 208 | 7. Disclaimer of Warranty. Unless required by applicable law or 209 | agreed to in writing, Licensor provides the Work (and each 210 | Contributor provides its Contributions) on an "AS IS" BASIS, 211 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 212 | implied, including, without limitation, any warranties or conditions 213 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 214 | PARTICULAR PURPOSE. You are solely responsible for determining the 215 | appropriateness of using or redistributing the Work and assume any 216 | risks associated with Your exercise of permissions under this License. 217 | 218 | 8. Limitation of Liability. In no event and under no legal theory, 219 | whether in tort (including negligence), contract, or otherwise, 220 | unless required by applicable law (such as deliberate and grossly 221 | negligent acts) or agreed to in writing, shall any Contributor be 222 | liable to You for damages, including any direct, indirect, special, 223 | incidental, or consequential damages of any character arising as a 224 | result of this License or out of the use or inability to use the 225 | Work (including but not limited to damages for loss of goodwill, 226 | work stoppage, computer failure or malfunction, or any and all 227 | other commercial damages or losses), even if such Contributor 228 | has been advised of the possibility of such damages. 229 | 230 | 9. Accepting Warranty or Additional Liability. While redistributing 231 | the Work or Derivative Works thereof, You may choose to offer, 232 | and charge a fee for, acceptance of support, warranty, indemnity, 233 | or other liability obligations and/or rights consistent with this 234 | License. However, in accepting such obligations, You may act only 235 | on Your own behalf and on Your sole responsibility, not on behalf 236 | of any other Contributor, and only if You agree to indemnify, 237 | defend, and hold each Contributor harmless for any liability 238 | incurred by, or claims asserted against, such Contributor by reason 239 | of your accepting any such warranty or additional liability. 240 | 241 | END OF TERMS AND CONDITIONS 242 | 243 | APPENDIX: How to apply the Apache License to your work. 244 | 245 | To apply the Apache License to your work, attach the following 246 | boilerplate notice, with the fields enclosed by brackets "[]" 247 | replaced with your own identifying information. (Don't include 248 | the brackets!) The text should be enclosed in the appropriate 249 | comment syntax for the file format. We also recommend that a 250 | file or class name and description of purpose be included on the 251 | same "printed page" as the copyright notice for easier 252 | identification within third-party archives. 253 | 254 | Copyright (c) 2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors 255 | 256 | Licensed under the Apache License, Version 2.0 (the "License"); 257 | you may not use this file except in compliance with the License. 258 | You may obtain a copy of the License at 259 | 260 | http://www.apache.org/licenses/LICENSE-2.0 261 | 262 | Unless required by applicable law or agreed to in writing, software 263 | distributed under the License is distributed on an "AS IS" BASIS, 264 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 265 | See the License for the specific language governing permissions and 266 | limitations under the License. 267 | 268 | 269 | 270 | zone.js 271 | MIT 272 | The MIT License 273 | 274 | Copyright (c) 2010-2022 Google LLC. https://angular.io/license 275 | 276 | Permission is hereby granted, free of charge, to any person obtaining a copy 277 | of this software and associated documentation files (the "Software"), to deal 278 | in the Software without restriction, including without limitation the rights 279 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 280 | copies of the Software, and to permit persons to whom the Software is 281 | furnished to do so, subject to the following conditions: 282 | 283 | The above copyright notice and this permission notice shall be included in 284 | all copies or substantial portions of the Software. 285 | 286 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 287 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 288 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 289 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 290 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 291 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 292 | THE SOFTWARE. 293 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsqlabs/ngx-guided-tour/0d75fc932c76a7d41a0b90b19c74fce73b110d8f/docs/favicon.ico -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | NgxGuidedTourDemo 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/polyfills.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkngx_guided_tour_demo=self.webpackChunkngx_guided_tour_demo||[]).push([[429],{435:(be,Re,Ce)=>{Ce(609)},609:function(be,Re,Ce){var Ee,Le,ye=this&&this.__spreadArray||function(se,le,De){if(De||2===arguments.length)for(var fe,Te=0,Ve=le.length;Te",this._properties=a&&a.properties||{},this._zoneDelegate=new y(this,this._parent&&this._parent._zoneDelegate,a)}return v.assertZonePatched=function(){if(e.Promise!==O.ZoneAwarePromise)throw new Error("Zone.js has detected that ZoneAwarePromise `(window|global).Promise` has been overwritten.\nMost likely cause is that a Promise polyfill has been loaded after Zone.js (Polyfilling Promise api is not necessary when zone.js is loaded. If you must load one, do so before loading zone.js.)")},Object.defineProperty(v,"root",{get:function(){for(var o=v.current;o.parent;)o=o.parent;return o},enumerable:!1,configurable:!0}),Object.defineProperty(v,"current",{get:function(){return W.zone},enumerable:!1,configurable:!0}),Object.defineProperty(v,"currentTask",{get:function(){return ae},enumerable:!1,configurable:!0}),v.__load_patch=function(o,a,i){if(void 0===i&&(i=!1),O.hasOwnProperty(o)){if(!i&&f)throw Error("Already loaded patch: "+o)}else if(!e["__Zone_disable_"+o]){var w="Zone:"+o;t(w),O[o]=a(e,v,X),n(w,w)}},Object.defineProperty(v.prototype,"parent",{get:function(){return this._parent},enumerable:!1,configurable:!0}),Object.defineProperty(v.prototype,"name",{get:function(){return this._name},enumerable:!1,configurable:!0}),v.prototype.get=function(o){var a=this.getZoneWith(o);if(a)return a._properties[o]},v.prototype.getZoneWith=function(o){for(var a=this;a;){if(a._properties.hasOwnProperty(o))return a;a=a._parent}return null},v.prototype.fork=function(o){if(!o)throw new Error("ZoneSpec required!");return this._zoneDelegate.fork(this,o)},v.prototype.wrap=function(o,a){if("function"!=typeof o)throw new Error("Expecting function got: "+o);var i=this._zoneDelegate.intercept(this,o,a),w=this;return function(){return w.runGuarded(i,this,arguments,a)}},v.prototype.run=function(o,a,i,w){W={parent:W,zone:this};try{return this._zoneDelegate.invoke(this,o,a,i,w)}finally{W=W.parent}},v.prototype.runGuarded=function(o,a,i,w){void 0===a&&(a=null),W={parent:W,zone:this};try{try{return this._zoneDelegate.invoke(this,o,a,i,w)}catch(Y){if(this._zoneDelegate.handleError(this,Y))throw Y}}finally{W=W.parent}},v.prototype.runTask=function(o,a,i){if(o.zone!=this)throw new Error("A task can only be run in the zone of creation! (Creation: "+(o.zone||m).name+"; Execution: "+this.name+")");if(o.state!==U||o.type!==N&&o.type!==P){var w=o.state!=B;w&&o._transitionTo(B,F),o.runCount++;var Y=ae;ae=o,W={parent:W,zone:this};try{o.type==P&&o.data&&!o.data.isPeriodic&&(o.cancelFn=void 0);try{return this._zoneDelegate.invokeTask(this,o,a,i)}catch(ce){if(this._zoneDelegate.handleError(this,ce))throw ce}}finally{o.state!==U&&o.state!==z&&(o.type==N||o.data&&o.data.isPeriodic?w&&o._transitionTo(F,B):(o.runCount=0,this._updateTaskCount(o,-1),w&&o._transitionTo(U,B,U))),W=W.parent,ae=Y}}},v.prototype.scheduleTask=function(o){if(o.zone&&o.zone!==this)for(var a=this;a;){if(a===o.zone)throw Error("can not reschedule task to ".concat(this.name," which is descendants of the original zone ").concat(o.zone.name));a=a.parent}o._transitionTo(x,U);var i=[];o._zoneDelegates=i,o._zone=this;try{o=this._zoneDelegate.scheduleTask(this,o)}catch(w){throw o._transitionTo(z,x,U),this._zoneDelegate.handleError(this,w),w}return o._zoneDelegates===i&&this._updateTaskCount(o,1),o.state==x&&o._transitionTo(F,x),o},v.prototype.scheduleMicroTask=function(o,a,i,w){return this.scheduleTask(new p(Z,o,a,i,w,void 0))},v.prototype.scheduleMacroTask=function(o,a,i,w,Y){return this.scheduleTask(new p(P,o,a,i,w,Y))},v.prototype.scheduleEventTask=function(o,a,i,w,Y){return this.scheduleTask(new p(N,o,a,i,w,Y))},v.prototype.cancelTask=function(o){if(o.zone!=this)throw new Error("A task can only be cancelled in the zone of creation! (Creation: "+(o.zone||m).name+"; Execution: "+this.name+")");o._transitionTo(k,F,B);try{this._zoneDelegate.cancelTask(this,o)}catch(a){throw o._transitionTo(z,k),this._zoneDelegate.handleError(this,a),a}return this._updateTaskCount(o,-1),o._transitionTo(U,k),o.runCount=0,o},v.prototype._updateTaskCount=function(o,a){var i=o._zoneDelegates;-1==a&&(o._zoneDelegates=null);for(var w=0;w0,macroTask:i.macroTask>0,eventTask:i.eventTask>0,change:o})},v}(),p=function(){function v(o,a,i,w,Y,ce){if(this._zone=null,this.runCount=0,this._zoneDelegates=null,this._state="notScheduled",this.type=o,this.source=a,this.data=w,this.scheduleFn=Y,this.cancelFn=ce,!i)throw new Error("callback is not defined");this.callback=i;var l=this;this.invoke=o===N&&w&&w.useG?v.invokeTask:function(){return v.invokeTask.call(e,l,this,arguments)}}return v.invokeTask=function(o,a,i){o||(o=this),Q++;try{return o.runCount++,o.zone.runTask(o,a,i)}finally{1==Q&&A(),Q--}},Object.defineProperty(v.prototype,"zone",{get:function(){return this._zone},enumerable:!1,configurable:!0}),Object.defineProperty(v.prototype,"state",{get:function(){return this._state},enumerable:!1,configurable:!0}),v.prototype.cancelScheduleRequest=function(){this._transitionTo(U,x)},v.prototype._transitionTo=function(o,a,i){if(this._state!==a&&this._state!==i)throw new Error("".concat(this.type," '").concat(this.source,"': can not transition to '").concat(o,"', expecting state '").concat(a,"'").concat(i?" or '"+i+"'":"",", was '").concat(this._state,"'."));this._state=o,o==U&&(this._zoneDelegates=null)},v.prototype.toString=function(){return this.data&&typeof this.data.handleId<"u"?this.data.handleId.toString():Object.prototype.toString.call(this)},v.prototype.toJSON=function(){return{type:this.type,state:this.state,source:this.source,zone:this.zone.name,runCount:this.runCount}},v}(),g=c("setTimeout"),T=c("Promise"),R=c("then"),C=[],H=!1;function V(v){if($||e[T]&&($=e[T].resolve(0)),$){var o=$[R];o||(o=$.then),o.call($,v)}else e[g](v,0)}function J(v){0===Q&&0===C.length&&V(A),v&&C.push(v)}function A(){if(!H){for(H=!0;C.length;){var v=C;C=[];for(var o=0;o=0;t--)"function"==typeof e[t]&&(e[t]=Xe(e[t],r+"_"+t));return e}function rr(e){return!e||!1!==e.writable&&!("function"==typeof e.get&&typeof e.set>"u")}var tr=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope,Ae=!("nw"in te)&&typeof te.process<"u"&&"[object process]"==={}.toString.call(te.process),Ke=!Ae&&!tr&&!(!Ne||!ke.HTMLElement),nr=typeof te.process<"u"&&"[object process]"==={}.toString.call(te.process)&&!tr&&!(!Ne||!ke.HTMLElement),je={},or=function(e){if(e=e||te.event){var r=je[e.type];r||(r=je[e.type]=G("ON_PROPERTY"+e.type));var u,t=this||e.target||te,n=t[r];return Ke&&t===ke&&"error"===e.type?!0===(u=n&&n.call(this,e.message,e.filename,e.lineno,e.colno,e.error))&&e.preventDefault():null!=(u=n&&n.apply(this,arguments))&&!u&&e.preventDefault(),u}};function ar(e,r,t){var n=se(e,r);if(!n&&t&&se(t,r)&&(n={enumerable:!0,configurable:!0}),n&&n.configurable){var c=G("on"+r+"patched");if(!e.hasOwnProperty(c)||!e[c]){delete n.writable,delete n.value;var f=n.get,d=n.set,E=r.substr(2),y=je[E];y||(y=je[E]=G("ON_PROPERTY"+E)),n.set=function(p){var g=this;!g&&e===te&&(g=te),g&&("function"==typeof g[y]&&g.removeEventListener(E,or),d&&d.call(g,null),g[y]=p,"function"==typeof p&&g.addEventListener(E,or,!1))},n.get=function(){var p=this;if(!p&&e===te&&(p=te),!p)return null;var g=p[y];if(g)return g;if(f){var T=f.call(this);if(T)return n.set.call(this,T),"function"==typeof p.removeAttribute&&p.removeAttribute(r),T}return null},le(e,r,n),e[c]=!0}}}function ir(e,r,t){if(r)for(var n=0;n=0&&"function"==typeof d[E.cbIdx]?qe(E.name,d[E.cbIdx],E,u):c.apply(f,d)}})}function _e(e,r){e[G("OriginalDelegate")]=r}var ur=!1,Je=!1;function Rr(){if(ur)return Je;ur=!0;try{var e=ke.navigator.userAgent;(-1!==e.indexOf("MSIE ")||-1!==e.indexOf("Trident/")||-1!==e.indexOf("Edge/"))&&(Je=!0)}catch{}return Je}Zone.__load_patch("ZoneAwarePromise",function(e,r,t){var n=Object.getOwnPropertyDescriptor,u=Object.defineProperty;var f=t.symbol,d=[],E=!0===e[f("DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION")],y=f("Promise"),p=f("then");t.onUnhandledError=function(l){if(t.showUncaughtError()){var _=l&&l.rejection;_?console.error("Unhandled Promise rejection:",_ instanceof Error?_.message:_,"; Zone:",l.zone.name,"; Task:",l.task&&l.task.source,"; Value:",_,_ instanceof Error?_.stack:void 0):console.error(l)}},t.microtaskDrainDone=function(){for(var l=function(){var _=d.shift();try{_.zone.runGuarded(function(){throw _.throwOriginal?_.rejection:_})}catch(h){!function R(l){t.onUnhandledError(l);try{var _=r[T];"function"==typeof _&&_.call(this,l)}catch{}}(h)}};d.length;)l()};var T=f("unhandledPromiseRejectionHandler");function C(l){return l&&l.then}function H(l){return l}function $(l){return a.reject(l)}var V=f("state"),J=f("value"),A=f("finally"),m=f("parentPromiseValue"),U=f("parentPromiseState"),F=null,B=!0,k=!1;function Z(l,_){return function(h){try{X(l,_,h)}catch(s){X(l,!1,s)}}}var O=f("currentTaskTrace");function X(l,_,h){var s=function(){var l=!1;return function(h){return function(){l||(l=!0,h.apply(null,arguments))}}}();if(l===h)throw new TypeError("Promise resolved with itself");if(l[V]===F){var b=null;try{("object"==typeof h||"function"==typeof h)&&(b=h&&h.then)}catch(L){return s(function(){X(l,!1,L)})(),l}if(_!==k&&h instanceof a&&h.hasOwnProperty(V)&&h.hasOwnProperty(J)&&h[V]!==F)ae(h),X(l,h[V],h[J]);else if(_!==k&&"function"==typeof b)try{b.call(h,s(Z(l,_)),s(Z(l,!1)))}catch(L){s(function(){X(l,!1,L)})()}else{l[V]=_;var D=l[J];if(l[J]=h,l[A]===A&&_===B&&(l[V]=l[U],l[J]=l[m]),_===k&&h instanceof Error){var S=r.currentTask&&r.currentTask.data&&r.currentTask.data.__creationTrace__;S&&u(h,O,{configurable:!0,enumerable:!1,writable:!0,value:S})}for(var M=0;M2}).map(function(r){return r.substring(2)})}function Ir(e,r){if((!Ae||nr)&&!Zone[e.symbol("patchEvents")]){var t=r.__Zone_ignore_on_properties,n=[];if(Ke){var u=window;n=n.concat(["Document","SVGElement","Element","HTMLElement","HTMLBodyElement","HTMLMediaElement","HTMLFrameSetElement","HTMLFrameElement","HTMLIFrameElement","HTMLMarqueeElement","Worker"]);var c=function Sr(){try{var e=ke.navigator.userAgent;if(-1!==e.indexOf("MSIE ")||-1!==e.indexOf("Trident/"))return!0}catch{}return!1}()?[{target:u,ignoreProperties:["error"]}]:[];dr(u,Qe(u),t&&t.concat(c),De(u))}n=n.concat(["XMLHttpRequest","XMLHttpRequestEventTarget","IDBIndex","IDBRequest","IDBOpenDBRequest","IDBDatabase","IDBTransaction","IDBCursor","WebSocket"]);for(var f=0;f"u"?delete t.configurable:t.configurable=n;try{return Fe(e,r,t)}catch(d){var u=!1;if(("createdCallback"===r||"attachedCallback"===r||"detachedCallback"===r||"attributeChangedCallback"===r)&&(u=!0),!u)throw d;var c=null;try{c=JSON.stringify(t)}catch{c=t.toString()}console.log("Attempting to configure '".concat(r,"' with descriptor '").concat(c,"' on object '").concat(e,"' and got error, giving up: ").concat(d))}}}function Hr(e,r){var t=e.getGlobalObjects();if((!t.isNode||t.isMix)&&!function xr(e,r){var t=e.getGlobalObjects();if((t.isBrowser||t.isMix)&&!e.ObjectGetOwnPropertyDescriptor(HTMLElement.prototype,"onclick")&&typeof Element<"u"){var c=e.ObjectGetOwnPropertyDescriptor(Element.prototype,"onclick");if(c&&!c.configurable)return!1;if(c){e.ObjectDefineProperty(Element.prototype,"onclick",{enumerable:!0,configurable:!0,get:function(){return!0}});var d=!!document.createElement("div").onclick;return e.ObjectDefineProperty(Element.prototype,"onclick",c),d}}var E=r.XMLHttpRequest;if(!E)return!1;var y="onreadystatechange",p=E.prototype,g=e.ObjectGetOwnPropertyDescriptor(p,y);if(g)return e.ObjectDefineProperty(p,y,{enumerable:!0,configurable:!0,get:function(){return!0}}),d=!!(T=new E).onreadystatechange,e.ObjectDefineProperty(p,y,g||{}),d;var R=e.symbol("fake");e.ObjectDefineProperty(p,y,{enumerable:!0,configurable:!0,get:function(){return this[R]},set:function(V){this[R]=V}});var T,C=function(){};return(T=new E).onreadystatechange=C,d=T[R]===C,T.onreadystatechange=null,d}(e,r)){var c=typeof WebSocket<"u";(function qr(e){for(var r=e.symbol("unbound"),t=function(u){var c=Tr[u],f="on"+c;self.addEventListener(c,function(d){var y,p,E=d.target;for(p=E?E.constructor.name+"."+f:"unknown."+f;E;)E[f]&&!E[f][r]&&((y=e.wrapWithCurrentZone(E[f],p))[r]=E[f],E[f]=y),E=E.parentElement},!0)},n=0;n1?new c(E,y):new c(E),R=e.ObjectGetOwnPropertyDescriptor(p,"onmessage");return R&&!1===R.configurable?(g=e.ObjectCreate(p),T=p,[n,u,"send","close"].forEach(function(C){g[C]=function(){var H=e.ArraySlice.call(arguments);if(C===n||C===u){var $=H.length>0?H[0]:void 0;if($){var V=Zone.__symbol__("ON_PROPERTY"+$);p[V]=g[V]}}return p[C].apply(p,H)}})):g=p,e.patchOnProperties(g,["close","error","message","open"],T),g};var f=r.WebSocket;for(var d in c)f[d]=c[d]}(e,r),Zone[e.symbol("patchEvents")]=!0}}Zone.__load_patch("util",function(e,r,t){var n=Qe(e);t.patchOnProperties=ir,t.patchMethod=de,t.bindArguments=Ye,t.patchMacroTask=Or;var u=r.__symbol__("BLACK_LISTED_EVENTS"),c=r.__symbol__("UNPATCHED_EVENTS");e[c]&&(e[u]=e[c]),e[u]&&(r[u]=r[c]=e[u]),t.patchEventPrototype=Zr,t.patchEventTarget=Dr,t.isIEOrEdge=Rr,t.ObjectDefineProperty=le,t.ObjectGetOwnPropertyDescriptor=se,t.ObjectCreate=Te,t.ArraySlice=Ve,t.patchClass=Me,t.wrapWithCurrentZone=Xe,t.filterProperties=hr,t.attachOriginToPatched=_e,t._redefineProperty=Object.defineProperty,t.patchCallbacks=Mr,t.getGlobalObjects=function(){return{globalSources:cr,zoneSymbolEventNames:ie,eventNames:n,isBrowser:Ke,isMix:nr,isNode:Ae,TRUE_STR:ve,FALSE_STR:he,ZONE_SYMBOL_PREFIX:Ze,ADD_EVENT_LISTENER_STR:fe,REMOVE_EVENT_LISTENER_STR:Ue}}});var e,r,Tr=ye(ye(ye(ye(ye(ye(ye(ye([],["abort","animationcancel","animationend","animationiteration","auxclick","beforeinput","blur","cancel","canplay","canplaythrough","change","compositionstart","compositionupdate","compositionend","cuechange","click","close","contextmenu","curechange","dblclick","drag","dragend","dragenter","dragexit","dragleave","dragover","drop","durationchange","emptied","ended","error","focus","focusin","focusout","gotpointercapture","input","invalid","keydown","keypress","keyup","load","loadstart","loadeddata","loadedmetadata","lostpointercapture","mousedown","mouseenter","mouseleave","mousemove","mouseout","mouseover","mouseup","mousewheel","orientationchange","pause","play","playing","pointercancel","pointerdown","pointerenter","pointerleave","pointerlockchange","mozpointerlockchange","webkitpointerlockerchange","pointerlockerror","mozpointerlockerror","webkitpointerlockerror","pointermove","pointout","pointerover","pointerup","progress","ratechange","reset","resize","scroll","seeked","seeking","select","selectionchange","selectstart","show","sort","stalled","submit","suspend","timeupdate","volumechange","touchcancel","touchmove","touchstart","touchend","transitioncancel","transitionend","waiting","wheel"],!0),["webglcontextrestored","webglcontextlost","webglcontextcreationerror"],!0),["autocomplete","autocompleteerror"],!0),["toggle"],!0),["afterscriptexecute","beforescriptexecute","DOMContentLoaded","freeze","fullscreenchange","mozfullscreenchange","webkitfullscreenchange","msfullscreenchange","fullscreenerror","mozfullscreenerror","webkitfullscreenerror","msfullscreenerror","readystatechange","visibilitychange","resume"],!0),["absolutedeviceorientation","afterinput","afterprint","appinstalled","beforeinstallprompt","beforeprint","beforeunload","devicelight","devicemotion","deviceorientation","deviceorientationabsolute","deviceproximity","hashchange","languagechange","message","mozbeforepaint","offline","online","paint","pageshow","pagehide","popstate","rejectionhandled","storage","unhandledrejection","unload","userproximity","vrdisplayconnected","vrdisplaydisconnected","vrdisplaypresentchange"],!0),["beforecopy","beforecut","beforepaste","copy","cut","paste","dragstart","loadend","animationstart","search","transitionrun","transitionstart","webkitanimationend","webkitanimationiteration","webkitanimationstart","webkittransitionend"],!0),["activate","afterupdate","ariarequest","beforeactivate","beforedeactivate","beforeeditfocus","beforeupdate","cellchange","controlselect","dataavailable","datasetchanged","datasetcomplete","errorupdate","filterchange","layoutcomplete","losecapture","move","moveend","movestart","propertychange","resizeend","resizestart","rowenter","rowexit","rowsdelete","rowsinserted","command","compassneedscalibration","deactivate","help","mscontentzoom","msmanipulationstatechanged","msgesturechange","msgesturedoubletap","msgestureend","msgesturehold","msgesturestart","msgesturetap","msgotpointercapture","msinertiastart","mslostpointercapture","mspointercancel","mspointerdown","mspointerenter","mspointerhover","mspointerleave","mspointermove","mspointerout","mspointerover","mspointerup","pointerout","mssitemodejumplistitemremoved","msthumbnailclick","stop","storagecommit"],!0);e=typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},r=e.__Zone_symbol_prefix||"__zone_symbol__",e[function t(n){return r+n}("legacyPatch")]=function(){var n=e.Zone;n.__load_patch("defineProperty",function(u,c,f){f._redefineProperty=Nr,function Lr(){xe=Zone.__symbol__,Fe=Object[xe("defineProperty")]=Object.defineProperty,_r=Object[xe("getOwnPropertyDescriptor")]=Object.getOwnPropertyDescriptor,pr=Object.create,me=xe("unconfigurables"),Object.defineProperty=function(e,r,t){if(Er(e,r))throw new TypeError("Cannot assign to read only property '"+r+"' of "+e);var n=t.configurable;return"prototype"!==r&&(t=$e(e,r,t)),yr(e,r,t,n)},Object.defineProperties=function(e,r){Object.keys(r).forEach(function(f){Object.defineProperty(e,f,r[f])});for(var t=0,n=Object.getOwnPropertySymbols(r);t0){var q=P.invoke;P.invoke=function(){for(var v=O[r.__symbol__("loadfalse")],o=0;o{be(be.s=435)}]); -------------------------------------------------------------------------------- /docs/runtime.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";var e,_={},i={};function a(e){var n=i[e];if(void 0!==n)return n.exports;var r=i[e]={id:e,loaded:!1,exports:{}};return _[e].call(r.exports,r,r.exports,a),r.loaded=!0,r.exports}a.m=_,e=[],a.O=(n,r,u,f)=>{if(!r){var o=1/0;for(t=0;t=f)&&Object.keys(a.O).every(p=>a.O[p](r[l]))?r.splice(l--,1):(s=!1,f0&&e[t-1][2]>f;t--)e[t]=e[t-1];e[t]=[r,u,f]},a.n=e=>{var n=e&&e.__esModule?()=>e.default:()=>e;return a.d(n,{a:n}),n},a.d=(e,n)=>{for(var r in n)a.o(n,r)&&!a.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:n[r]})},a.o=(e,n)=>Object.prototype.hasOwnProperty.call(e,n),a.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e={666:0};a.O.j=u=>0===e[u];var n=(u,f)=>{var l,c,[t,o,s]=f,d=0;if(t.some(h=>0!==e[h])){for(l in o)a.o(o,l)&&(a.m[l]=o[l]);if(s)var v=s(a)}for(u&&u(f);d { 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 ngx-guided-tour-demo!'); 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 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/') as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-guided-tour-demo", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "build:all": "ng build && npm run build:lib", 9 | "build:lib": "ng-packagr -p projects/ngx-guided-tour/package.json && npm run build:lib:css && npm run build:lib:scss", 10 | "build:lib:css": "sass ./projects/ngx-guided-tour/src/lib/guided-tour-base-theme.scss ./dist/ngx-guided-tour/css/guided-tour-base-theme.css && sass ./projects/ngx-guided-tour/src/lib/guided-tour.component.scss ./dist/ngx-guided-tour/css/guided-tour.component.css", 11 | "build:lib:scss": "mkdir ./dist/ngx-guided-tour/scss/ && cp ./projects/ngx-guided-tour/src/lib/guided-tour-base-theme.scss ./dist/ngx-guided-tour/scss/guided-tour-base-theme.scss && cp ./projects/ngx-guided-tour/src/lib/guided-tour.component.scss ./dist/ngx-guided-tour/scss/guided-tour.component.scss", 12 | "test": "ng test", 13 | "lint": "ng lint", 14 | "e2e": "ng e2e", 15 | "publish": "cp README.md dist/ngx-guided-tour/README.md && cd dist/ngx-guided-tour && npm publish" 16 | }, 17 | "private": true, 18 | "dependencies": { 19 | "@angular/animations": "^13.0.0", 20 | "@angular/common": "^13.0.0", 21 | "@angular/compiler": "^13.0.0", 22 | "@angular/core": "^13.0.0", 23 | "@angular/forms": "^13.0.0", 24 | "@angular/platform-browser": "^13.0.0", 25 | "@angular/platform-browser-dynamic": "^13.0.0", 26 | "@angular/router": "^13.0.0", 27 | "bootstrap": "4.3.1", 28 | "core-js": "^3.21.1", 29 | "rxjs": "^6.6.7", 30 | "zone.js": "0.11.5" 31 | }, 32 | "devDependencies": { 33 | "@angular-devkit/build-angular": "^13.3.0", 34 | "@angular/cli": "^13.0.0", 35 | "@angular/compiler-cli": "^13.0.0", 36 | "@angular/language-service": "^13.0.0", 37 | "@types/jasmine": "2.8.16", 38 | "@types/jasminewd2": "2.0.6", 39 | "@types/node": "^16.0.0", 40 | "codelyzer": "6.0.2", 41 | "jasmine-core": "4.0.1", 42 | "jasmine-spec-reporter": "7.0.0", 43 | "karma": "6.3.17", 44 | "karma-chrome-launcher": "3.1.1", 45 | "karma-coverage-istanbul-reporter": "2.0.5", 46 | "karma-jasmine": "~4.0.1", 47 | "karma-jasmine-html-reporter": "~1.7.0", 48 | "ng-packagr": "^13.3.0", 49 | "normalize-url": "6.1.0", 50 | "protractor": "7.0.0", 51 | "sass": "^1.49.9", 52 | "serialize-javascript": "6.0.0", 53 | "trim-newlines": "3.0.1", 54 | "ts-node": "10.7.0", 55 | "tslib": "^2.3.0", 56 | "tslint": "5.11.0", 57 | "typescript": "^4.6.0" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /projects/ngx-guided-tour/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'), 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/ngx-guided-tour/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/ngx-guided-tour", 4 | "lib": { 5 | "entryFile": "src/public_api.ts" 6 | } 7 | } -------------------------------------------------------------------------------- /projects/ngx-guided-tour/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-guided-tour", 3 | "version": "2.0.1", 4 | "description": "Guided tour for your Angular applications.", 5 | "peerDependencies": { 6 | "@angular/common": ">=13.0.0", 7 | "@angular/core": ">=13.0.0", 8 | "bootstrap": "^4.0.0" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/lsqlabs/ngx-guided-tour.git" 13 | }, 14 | "keywords": [ 15 | "guided", 16 | "walkthrough", 17 | "tour", 18 | "angular", 19 | "angular6", 20 | "angular7", 21 | "SASS", 22 | "rxjs", 23 | "customizable", 24 | "ng", 25 | "ngx", 26 | "ng2" 27 | ], 28 | "author": { 29 | "name": "lsqlabs", 30 | "email": "developers@lsq.com" 31 | }, 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/lsqlabs/ngx-guided-tour/issues" 35 | }, 36 | "homepage": "https://github.com/lsqlabs/ngx-guided-tour#readme", 37 | "ngPackage": { 38 | "lib": { 39 | "entryFile": "src/public_api.ts" 40 | }, 41 | "dest": "../../dist/ngx-guided-tour" 42 | }, 43 | "exports": { 44 | "./scss/guided-tour-base-theme.scss": { 45 | "default": "./scss/guided-tour-base-theme.scss" 46 | }, 47 | "./scss/guided-tour.component.scss": { 48 | "default": "./scss/guided-tour.component.scss" 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /projects/ngx-guided-tour/src/lib/guided-tour-base-theme.scss: -------------------------------------------------------------------------------- 1 | $tour-zindex: 1081 !default; 2 | $tour-step-color: #ffffff !default; 3 | $tour-text-color: #231f1f !default; 4 | $tour-next-button-color: #007bff !default; 5 | $tour-next-button-hover: #0069d9 !default; 6 | $tour-back-button-color: #007bff !default; 7 | $tour-next-text-color: #ffffff !default; 8 | $tour-next-text-hover: #ffffff !default; 9 | $tour-skip-link-color: #5e5e5e !default; 10 | $tour-orb-color: #625aff !default; 11 | $tour-shadow-color: #4c4c4c !default; 12 | 13 | body.tour-open { 14 | overflow: hidden; 15 | } 16 | 17 | @mixin tour-triangle($direction, $color: currentColor, $size: 1rem) { 18 | 19 | @if not index(top right bottom left, $direction) { 20 | @error 'Direction must be either `top`, `right`, `bottom` or `left`.'; 21 | } 22 | 23 | $opposite-direction: top; 24 | 25 | @if $direction==top { 26 | $opposite-direction: bottom; 27 | } 28 | 29 | @if $direction==bottom { 30 | $opposite-direction: top; 31 | } 32 | 33 | @if $direction==right { 34 | $opposite-direction: left; 35 | } 36 | 37 | @if $direction==left { 38 | $opposite-direction: right; 39 | } 40 | 41 | width: 0; 42 | height: 0; 43 | content: ''; 44 | z-index: 2; 45 | border-#{$opposite-direction}: $size solid $color; 46 | $perpendicular-borders: $size solid transparent; 47 | @if $direction==top or $direction==bottom { 48 | border-left: $perpendicular-borders; 49 | border-right: $perpendicular-borders; 50 | } 51 | @else if $direction==right or $direction==left { 52 | border-bottom: $perpendicular-borders; 53 | border-top: $perpendicular-borders; 54 | } 55 | } 56 | 57 | ngx-guided-tour { 58 | .guided-tour-user-input-mask { 59 | z-index: $tour-zindex; 60 | } 61 | 62 | .guided-tour-spotlight-overlay { 63 | z-index: $tour-zindex + 1; 64 | } 65 | 66 | .tour-orb { 67 | z-index: $tour-zindex - 2; 68 | background-color: $tour-orb-color; 69 | box-shadow: 0 0 0.3rem 0.1rem $tour-orb-color; 70 | 71 | .tour-orb-ring { 72 | &::after { 73 | border: 1rem solid $tour-orb-color; 74 | box-shadow: 0 0 0.1rem 0.1rem $tour-orb-color; 75 | } 76 | } 77 | } 78 | 79 | .tour-step { 80 | z-index: $tour-zindex + 2; 81 | 82 | &.tour-bottom, &.tour-bottom-right, &.tour-bottom-left { 83 | .tour-arrow::before { 84 | @include tour-triangle(top, $tour-step-color); 85 | } 86 | } 87 | 88 | &.tour-top, &.tour-top-right, &.tour-top-left { 89 | .tour-arrow::before { 90 | @include tour-triangle(bottom, $tour-step-color); 91 | } 92 | } 93 | 94 | &.tour-left { 95 | .tour-arrow::before { 96 | @include tour-triangle(right, $tour-step-color); 97 | } 98 | } 99 | 100 | &.tour-right { 101 | .tour-arrow::before { 102 | @include tour-triangle(left, $tour-step-color); 103 | } 104 | } 105 | 106 | .tour-block { 107 | color: $tour-text-color; 108 | background-color: $tour-step-color; 109 | box-shadow: 0 0.4rem 0.6rem $tour-shadow-color; 110 | } 111 | 112 | .tour-buttons { 113 | button.skip-button { 114 | color: $tour-skip-link-color; 115 | } 116 | 117 | .back-button { 118 | color: $tour-back-button-color; 119 | } 120 | 121 | .next-button { 122 | background-color: $tour-next-button-color; 123 | color: $tour-next-text-color; 124 | &:hover { 125 | background-color: $tour-next-button-hover; 126 | color: $tour-next-text-hover; 127 | } 128 | } 129 | } 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /projects/ngx-guided-tour/src/lib/guided-tour.component.scss: -------------------------------------------------------------------------------- 1 | ngx-guided-tour { 2 | .guided-tour-user-input-mask { 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | display: block; 7 | height: 100%; 8 | width: 100%; 9 | max-height: 100vh; 10 | text-align: center; 11 | opacity: 0; 12 | } 13 | 14 | .guided-tour-spotlight-overlay { 15 | position: fixed; 16 | box-shadow: 0 0 0 9999px rgba(0,0,0,.7), 0 0 1.5rem rgba(0,0,0,.5); 17 | } 18 | 19 | .tour-orb { 20 | position: fixed; 21 | width: 20px; 22 | height: 20px; 23 | border-radius: 50%; 24 | 25 | .tour-orb-ring { 26 | width: 35px; 27 | height: 35px; 28 | position: relative; 29 | top: 50%; 30 | left: 50%; 31 | transform: translate(-50%, -50%); 32 | animation: pulse 2s linear infinite; 33 | 34 | &:after { 35 | content: ''; 36 | display: inline-block; 37 | height: 100%; 38 | width: 100%; 39 | border-radius: 50%; 40 | } 41 | } 42 | 43 | @keyframes pulse { 44 | from { 45 | transform: translate(-50%, -50%) scale(0.45); 46 | opacity: 1.0; 47 | } 48 | to { 49 | transform: translate(-50%, -50%) scale(1); 50 | opacity: 0.0; 51 | } 52 | } 53 | } 54 | 55 | .tour-step { 56 | position: fixed; 57 | &.page-tour-step { 58 | max-width: 400px; 59 | width: 50%; 60 | left: 50%; 61 | top: 50%; 62 | transform: translate(-50%, -50%) 63 | } 64 | &.tour-bottom, &.tour-bottom-right, &.tour-bottom-left { 65 | .tour-arrow::before { 66 | position: absolute; 67 | } 68 | .tour-block { 69 | margin-top: 10px; 70 | } 71 | } 72 | 73 | &.tour-top, &.tour-top-right, &.tour-top-left { 74 | margin-bottom: 10px; 75 | 76 | .tour-arrow::before { 77 | position: absolute; 78 | bottom: 0; 79 | } 80 | .tour-block { 81 | margin-bottom: 10px; 82 | } 83 | } 84 | 85 | &.tour-bottom , &.tour-top { 86 | .tour-arrow::before { 87 | transform: translateX(-50%); 88 | left: 50%; 89 | } 90 | } 91 | 92 | &.tour-bottom-right, &.tour-top-right { 93 | .tour-arrow::before { 94 | transform: translateX(-100%); 95 | left: calc(100% - 5px); 96 | } 97 | } 98 | 99 | &.tour-bottom-left, &.tour-top-left { 100 | .tour-arrow::before { 101 | left: 5px; 102 | } 103 | } 104 | 105 | &.tour-left { 106 | .tour-arrow::before { 107 | position: absolute; 108 | left: 100%; 109 | transform: translateX(-100%); 110 | top: 5px; 111 | } 112 | .tour-block { 113 | margin-right: 10px; 114 | } 115 | } 116 | 117 | &.tour-right { 118 | .tour-arrow::before { 119 | position: absolute; 120 | left: 0; 121 | top: 5px; 122 | } 123 | .tour-block { 124 | margin-left: 10px; 125 | } 126 | } 127 | 128 | .tour-block { 129 | padding: 15px 25px; 130 | } 131 | 132 | .tour-progress-indicator { 133 | padding-bottom: 15px; 134 | } 135 | 136 | .tour-title { 137 | font-weight: bold !important; 138 | padding-bottom: 20px; 139 | } 140 | 141 | h3.tour-title { 142 | font-size: 20px; 143 | } 144 | h2.tour-title { 145 | font-size: 30px; 146 | } 147 | 148 | .tour-content { 149 | min-height: 80px; 150 | padding-bottom: 30px; 151 | font-size: 15px; 152 | } 153 | 154 | .tour-buttons { 155 | overflow: hidden; // clearfix 156 | 157 | button.link-button { 158 | padding-left: 0; 159 | font-size: 15px; 160 | font-weight: bold; 161 | max-width: none !important; 162 | cursor: pointer; 163 | text-align: center; 164 | white-space: nowrap; 165 | vertical-align: middle; 166 | border: 1px solid transparent; 167 | line-height: 1.5; 168 | background-color: transparent; 169 | position: relative; 170 | outline: none; 171 | padding: 0 15px; 172 | -webkit-appearance: button; 173 | } 174 | 175 | button.skip-button.link-button { 176 | padding-left: 0; 177 | border-left: 0; 178 | } 179 | 180 | .back-button { 181 | float: right; 182 | } 183 | 184 | .next-button { 185 | cursor: pointer; 186 | border-radius: 1px; 187 | float: right; 188 | font-size: 14px; 189 | border: none; 190 | outline: none; 191 | padding-left: 10px; 192 | padding-right: 10px; 193 | } 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /projects/ngx-guided-tour/src/lib/guided-tour.component.ts: -------------------------------------------------------------------------------- 1 | import { AfterViewInit, Component, ElementRef, Input, OnDestroy, ViewChild, ViewEncapsulation, TemplateRef, Inject } from '@angular/core'; 2 | import { fromEvent, Subscription } from 'rxjs'; 3 | import { DOCUMENT } from '@angular/common'; 4 | import { Orientation, TourStep, ProgressIndicatorLocation } from './guided-tour.constants'; 5 | import { GuidedTourService } from './guided-tour.service'; 6 | import { WindowRefService } from "./windowref.service"; 7 | 8 | @Component({ 9 | selector: 'ngx-guided-tour', 10 | template: ` 11 |
17 |
18 |
19 |
20 |
21 |
26 |
27 |
28 |
29 |
38 |
39 |
40 |
44 | 45 |
46 |

47 | {{ currentTourStep.title }} 48 |

49 |

50 | {{ currentTourStep.title }} 51 |

52 |
53 |
54 | 59 | 67 | 72 | 73 | 78 | 83 |
84 |
85 |
86 |
87 | 88 | 92 | 93 | 94 |  {{ currentStepNumber }}/{{ totalSteps }} 95 | 96 | `, 97 | styleUrls: ['./guided-tour.component.scss'], 98 | encapsulation: ViewEncapsulation.None 99 | }) 100 | export class GuidedTourComponent implements AfterViewInit, OnDestroy { 101 | @Input() public topOfPageAdjustment ?= 0; 102 | @Input() public tourStepWidth ?= 300; 103 | @Input() public minimalTourStepWidth ?= 200; 104 | @Input() public skipText ?= 'Skip'; 105 | @Input() public nextText ?= 'Next'; 106 | @Input() public doneText ?= 'Done'; 107 | @Input() public closeText ?= 'Close'; 108 | @Input() public backText ?= 'Back'; 109 | @Input() public progressIndicatorLocation?: ProgressIndicatorLocation = ProgressIndicatorLocation.InsideNextButton; 110 | @Input() public progressIndicator?: TemplateRef = undefined; 111 | @ViewChild('tourStep', { static: false }) public tourStep: ElementRef; 112 | public highlightPadding = 4; 113 | public currentTourStep: TourStep = null; 114 | public selectedElementRect: DOMRect = null; 115 | public isOrbShowing = false; 116 | public progressIndicatorLocations = ProgressIndicatorLocation; 117 | 118 | private resizeSubscription: Subscription; 119 | private scrollSubscription: Subscription; 120 | 121 | constructor( 122 | public guidedTourService: GuidedTourService, 123 | private windowRef: WindowRefService, 124 | @Inject(DOCUMENT) private dom: any 125 | ) { } 126 | 127 | private get maxWidthAdjustmentForTourStep(): number { 128 | return this.tourStepWidth - this.minimalTourStepWidth; 129 | } 130 | 131 | private get widthAdjustmentForScreenBound(): number { 132 | if (!this.tourStep) { 133 | return 0; 134 | } 135 | let adjustment = 0; 136 | if (this.calculatedLeftPosition < 0) { 137 | adjustment = -this.calculatedLeftPosition; 138 | } 139 | if (this.calculatedLeftPosition > this.windowRef.nativeWindow.innerWidth - this.tourStepWidth) { 140 | adjustment = this.calculatedLeftPosition - (this.windowRef.nativeWindow.innerWidth - this.tourStepWidth); 141 | } 142 | 143 | return Math.min(this.maxWidthAdjustmentForTourStep, adjustment); 144 | } 145 | 146 | public get calculatedTourStepWidth() { 147 | return this.tourStepWidth - this.widthAdjustmentForScreenBound; 148 | } 149 | 150 | public ngAfterViewInit(): void { 151 | this.guidedTourService.guidedTourCurrentStepStream.subscribe((step: TourStep) => { 152 | this.currentTourStep = step; 153 | if (step && step.selector) { 154 | const selectedElement = this.dom.querySelector(step.selector); 155 | if (selectedElement) { 156 | this.scrollToAndSetElement(); 157 | } else { 158 | this.selectedElementRect = null; 159 | } 160 | } else { 161 | this.selectedElementRect = null; 162 | } 163 | }); 164 | 165 | this.guidedTourService.guidedTourOrbShowingStream.subscribe((value: boolean) => { 166 | this.isOrbShowing = value; 167 | }); 168 | 169 | this.resizeSubscription = fromEvent(this.windowRef.nativeWindow, 'resize').subscribe(() => { 170 | this.updateStepLocation(); 171 | }); 172 | 173 | this.scrollSubscription = fromEvent(this.windowRef.nativeWindow, 'scroll').subscribe(() => { 174 | this.updateStepLocation(); 175 | }); 176 | } 177 | 178 | public ngOnDestroy(): void { 179 | this.resizeSubscription?.unsubscribe(); 180 | this.scrollSubscription?.unsubscribe(); 181 | } 182 | 183 | public scrollToAndSetElement(): void { 184 | this.updateStepLocation(); 185 | // Allow things to render to scroll to the correct location 186 | setTimeout(() => { 187 | if (!this.isOrbShowing && !this.isTourOnScreen()) { 188 | if (this.selectedElementRect && this.isBottom()) { 189 | // Scroll so the element is on the top of the screen. 190 | const topPos = ((this.windowRef.nativeWindow.scrollY + this.selectedElementRect.top) - this.topOfPageAdjustment) 191 | - (this.currentTourStep.scrollAdjustment ? this.currentTourStep.scrollAdjustment : 0) 192 | + this.getStepScreenAdjustment(); 193 | try { 194 | this.windowRef.nativeWindow.scrollTo({ 195 | left: null, 196 | top: topPos, 197 | behavior: 'smooth' 198 | }); 199 | } catch (err) { 200 | if (err instanceof TypeError) { 201 | this.windowRef.nativeWindow.scroll(0, topPos); 202 | } else { 203 | throw err; 204 | } 205 | } 206 | } else { 207 | // Scroll so the element is on the bottom of the screen. 208 | const topPos = (this.windowRef.nativeWindow.scrollY + this.selectedElementRect.top + this.selectedElementRect.height) 209 | - this.windowRef.nativeWindow.innerHeight 210 | + (this.currentTourStep.scrollAdjustment ? this.currentTourStep.scrollAdjustment : 0) 211 | - this.getStepScreenAdjustment(); 212 | try { 213 | this.windowRef.nativeWindow.scrollTo({ 214 | left: null, 215 | top: topPos, 216 | behavior: 'smooth' 217 | }); 218 | } catch (err) { 219 | if (err instanceof TypeError) { 220 | this.windowRef.nativeWindow.scroll(0, topPos); 221 | } else { 222 | throw err; 223 | } 224 | } 225 | } 226 | } 227 | }); 228 | } 229 | 230 | public handleOrb(): void { 231 | this.guidedTourService.activateOrb(); 232 | if (this.currentTourStep && this.currentTourStep.selector) { 233 | this.scrollToAndSetElement(); 234 | } 235 | } 236 | 237 | private isTourOnScreen(): boolean { 238 | return this.tourStep 239 | && this.elementInViewport(this.dom.querySelector(this.currentTourStep.selector)) 240 | && this.elementInViewport(this.tourStep.nativeElement); 241 | } 242 | 243 | // Modified from https://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport 244 | private elementInViewport(element: HTMLElement): boolean { 245 | let top = element.offsetTop; 246 | const height = element.offsetHeight; 247 | 248 | while (element.offsetParent) { 249 | element = (element.offsetParent as HTMLElement); 250 | top += element.offsetTop; 251 | } 252 | if (this.isBottom()) { 253 | return ( 254 | top >= (this.windowRef.nativeWindow.pageYOffset 255 | + this.topOfPageAdjustment 256 | + (this.currentTourStep.scrollAdjustment ? this.currentTourStep.scrollAdjustment : 0) 257 | + this.getStepScreenAdjustment()) 258 | && (top + height) <= (this.windowRef.nativeWindow.pageYOffset + this.windowRef.nativeWindow.innerHeight) 259 | ); 260 | } else { 261 | return ( 262 | top >= (this.windowRef.nativeWindow.pageYOffset + this.topOfPageAdjustment - this.getStepScreenAdjustment()) 263 | && (top + height + (this.currentTourStep.scrollAdjustment ? this.currentTourStep.scrollAdjustment : 0)) <= (this.windowRef.nativeWindow.pageYOffset + this.windowRef.nativeWindow.innerHeight) 264 | ); 265 | } 266 | } 267 | 268 | public backdropClick(event: Event): void { 269 | if (this.guidedTourService.preventBackdropFromAdvancing) { 270 | event.stopPropagation(); 271 | } else { 272 | this.guidedTourService.nextStep(); 273 | } 274 | } 275 | 276 | public updateStepLocation(): void { 277 | if (this.currentTourStep && this.currentTourStep.selector) { 278 | const selectedElement = this.dom.querySelector(this.currentTourStep.selector); 279 | if (selectedElement && typeof selectedElement.getBoundingClientRect === 'function') { 280 | this.selectedElementRect = (selectedElement.getBoundingClientRect() as DOMRect); 281 | } else { 282 | this.selectedElementRect = null; 283 | } 284 | } else { 285 | this.selectedElementRect = null; 286 | } 287 | } 288 | 289 | private isBottom(): boolean { 290 | return this.currentTourStep.orientation 291 | && (this.currentTourStep.orientation === Orientation.Bottom 292 | || this.currentTourStep.orientation === Orientation.BottomLeft 293 | || this.currentTourStep.orientation === Orientation.BottomRight); 294 | } 295 | 296 | public get topPosition(): number { 297 | const paddingAdjustment = this.getHighlightPadding(); 298 | 299 | if (this.isBottom()) { 300 | return this.selectedElementRect.top + this.selectedElementRect.height + paddingAdjustment; 301 | } 302 | 303 | return this.selectedElementRect.top - this.getHighlightPadding(); 304 | } 305 | 306 | public get orbTopPosition(): number { 307 | if (this.isBottom()) { 308 | return this.selectedElementRect.top + this.selectedElementRect.height; 309 | } 310 | 311 | if ( 312 | this.currentTourStep.orientation === Orientation.Right 313 | || this.currentTourStep.orientation === Orientation.Left 314 | ) { 315 | return (this.selectedElementRect.top + (this.selectedElementRect.height / 2)); 316 | } 317 | 318 | return this.selectedElementRect.top; 319 | } 320 | 321 | private get calculatedLeftPosition(): number { 322 | const paddingAdjustment = this.getHighlightPadding(); 323 | 324 | if ( 325 | this.currentTourStep.orientation === Orientation.TopRight 326 | || this.currentTourStep.orientation === Orientation.BottomRight 327 | ) { 328 | return (this.selectedElementRect.right - this.tourStepWidth); 329 | } 330 | 331 | if ( 332 | this.currentTourStep.orientation === Orientation.TopLeft 333 | || this.currentTourStep.orientation === Orientation.BottomLeft 334 | ) { 335 | return (this.selectedElementRect.left); 336 | } 337 | 338 | if (this.currentTourStep.orientation === Orientation.Left) { 339 | return this.selectedElementRect.left - this.tourStepWidth - paddingAdjustment; 340 | } 341 | 342 | if (this.currentTourStep.orientation === Orientation.Right) { 343 | return (this.selectedElementRect.left + this.selectedElementRect.width + paddingAdjustment); 344 | } 345 | 346 | return (this.selectedElementRect.right - (this.selectedElementRect.width / 2) - (this.tourStepWidth / 2)); 347 | } 348 | 349 | public get leftPosition(): number { 350 | if (this.calculatedLeftPosition >= 0) { 351 | return this.calculatedLeftPosition; 352 | } 353 | const adjustment = Math.max(0, -this.calculatedLeftPosition) 354 | const maxAdjustment = Math.min(this.maxWidthAdjustmentForTourStep, adjustment); 355 | return this.calculatedLeftPosition + maxAdjustment; 356 | } 357 | 358 | public get orbLeftPosition(): number { 359 | if ( 360 | this.currentTourStep.orientation === Orientation.TopRight 361 | || this.currentTourStep.orientation === Orientation.BottomRight 362 | ) { 363 | return this.selectedElementRect.right; 364 | } 365 | 366 | if ( 367 | this.currentTourStep.orientation === Orientation.TopLeft 368 | || this.currentTourStep.orientation === Orientation.BottomLeft 369 | ) { 370 | return this.selectedElementRect.left; 371 | } 372 | 373 | if (this.currentTourStep.orientation === Orientation.Left) { 374 | return this.selectedElementRect.left; 375 | } 376 | 377 | if (this.currentTourStep.orientation === Orientation.Right) { 378 | return (this.selectedElementRect.left + this.selectedElementRect.width); 379 | } 380 | 381 | return (this.selectedElementRect.right - (this.selectedElementRect.width / 2)); 382 | } 383 | 384 | public get transform(): string { 385 | if ( 386 | !this.currentTourStep.orientation 387 | || this.currentTourStep.orientation === Orientation.Top 388 | || this.currentTourStep.orientation === Orientation.TopRight 389 | || this.currentTourStep.orientation === Orientation.TopLeft 390 | ) { 391 | return 'translateY(-100%)'; 392 | } 393 | return null; 394 | } 395 | 396 | public get orbTransform(): string { 397 | if ( 398 | !this.currentTourStep.orientation 399 | || this.currentTourStep.orientation === Orientation.Top 400 | || this.currentTourStep.orientation === Orientation.Bottom 401 | || this.currentTourStep.orientation === Orientation.TopLeft 402 | || this.currentTourStep.orientation === Orientation.BottomLeft 403 | ) { 404 | return 'translateY(-50%)'; 405 | } 406 | 407 | if ( 408 | this.currentTourStep.orientation === Orientation.TopRight 409 | || this.currentTourStep.orientation === Orientation.BottomRight 410 | ) { 411 | return 'translate(-100%, -50%)'; 412 | } 413 | 414 | if ( 415 | this.currentTourStep.orientation === Orientation.Right 416 | || this.currentTourStep.orientation === Orientation.Left 417 | ) { 418 | return 'translate(-50%, -50%)'; 419 | } 420 | 421 | return null; 422 | } 423 | 424 | public get overlayTop(): number { 425 | if (this.selectedElementRect) { 426 | return this.selectedElementRect.top - this.getHighlightPadding(); 427 | } 428 | return 0; 429 | } 430 | 431 | public get overlayLeft(): number { 432 | if (this.selectedElementRect) { 433 | return this.selectedElementRect.left - this.getHighlightPadding(); 434 | } 435 | return 0; 436 | } 437 | 438 | public get overlayHeight(): number { 439 | if (this.selectedElementRect) { 440 | return this.selectedElementRect.height + (this.getHighlightPadding() * 2); 441 | } 442 | return 0; 443 | } 444 | 445 | public get overlayWidth(): number { 446 | if (this.selectedElementRect) { 447 | return this.selectedElementRect.width + (this.getHighlightPadding() * 2); 448 | } 449 | return 0; 450 | } 451 | 452 | private getHighlightPadding(): number { 453 | let paddingAdjustment = this.currentTourStep.useHighlightPadding ? this.highlightPadding : 0; 454 | if (this.currentTourStep.highlightPadding) { 455 | paddingAdjustment = this.currentTourStep.highlightPadding; 456 | } 457 | return paddingAdjustment; 458 | } 459 | 460 | // This calculates a value to add or subtract so the step should not be off screen. 461 | private getStepScreenAdjustment(): number { 462 | if ( 463 | this.currentTourStep.orientation === Orientation.Left 464 | || this.currentTourStep.orientation === Orientation.Right 465 | ) { 466 | return 0; 467 | } 468 | 469 | const scrollAdjustment = this.currentTourStep.scrollAdjustment ? this.currentTourStep.scrollAdjustment : 0; 470 | const tourStepHeight = typeof this.tourStep.nativeElement.getBoundingClientRect === 'function' ? this.tourStep.nativeElement.getBoundingClientRect().height : 0; 471 | const elementHeight = this.selectedElementRect.height + scrollAdjustment + tourStepHeight; 472 | 473 | if ((this.windowRef.nativeWindow.innerHeight - this.topOfPageAdjustment) < elementHeight) { 474 | return elementHeight - (this.windowRef.nativeWindow.innerHeight - this.topOfPageAdjustment); 475 | } 476 | return 0; 477 | } 478 | } 479 | -------------------------------------------------------------------------------- /projects/ngx-guided-tour/src/lib/guided-tour.constants.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface TourStep { 3 | /** Selector for element that will be highlighted */ 4 | selector?: string; 5 | /** Tour title text */ 6 | title?: string; 7 | /** Tour step text */ 8 | content: string; 9 | /** Where the tour step will appear next to the selected element */ 10 | orientation?: Orientation | OrientationConfiguration[]; 11 | /** Action that happens when the step is opened */ 12 | action?: () => void; 13 | /** Action that happens when the step is closed */ 14 | closeAction?: () => void; 15 | /** Skips this step, this is so you do not have create multiple tour configurations based on user settings/configuration */ 16 | skipStep?: boolean; 17 | /** Adds some padding for things like sticky headers when scrolling to an element */ 18 | scrollAdjustment?: number; 19 | /** Adds default padding around tour highlighting. Does not need to be true for highlightPadding to work */ 20 | useHighlightPadding?: boolean; 21 | /** Adds padding around tour highlighting in pixels, this overwrites the default for this step. Is not dependent on useHighlightPadding being true */ 22 | highlightPadding?: number; 23 | } 24 | 25 | export interface GuidedTour { 26 | /** Identifier for tour */ 27 | tourId: string; 28 | /** Use orb to start tour */ 29 | useOrb?: boolean; 30 | /** Steps fo the tour */ 31 | steps: TourStep[]; 32 | /** Function will be called when tour is skipped */ 33 | skipCallback?: (stepSkippedOn: number) => void; 34 | /** Function will be called when tour is completed */ 35 | completeCallback?: () => void; 36 | /** Minimum size of screen in pixels before the tour is run, if the tour is resized below this value the user will be told to resize */ 37 | minimumScreenSize?: number; 38 | /** Dialog shown if the window width is smaller than the defined minimum screen size. */ 39 | resizeDialog?: { 40 | /** Resize dialog title text */ 41 | title?: string; 42 | /** Resize dialog text */ 43 | content: string; 44 | } 45 | /** 46 | * Prevents the tour from advancing by clicking the backdrop. 47 | * This should only be set if you are completely sure your tour is displaying correctly on all screen sizes otherwise a user can get stuck. 48 | */ 49 | preventBackdropFromAdvancing?: boolean; 50 | } 51 | 52 | export interface OrientationConfiguration { 53 | /** Where the tour step will appear next to the selected element */ 54 | orientationDirection: Orientation; 55 | /** When this orientation configuration starts in pixels */ 56 | maximumSize?: number; 57 | } 58 | 59 | export class Orientation { 60 | public static readonly Bottom = 'bottom'; 61 | public static readonly BottomLeft = 'bottom-left'; 62 | public static readonly BottomRight = 'bottom-right'; 63 | public static readonly Center = 'center'; 64 | public static readonly Left = 'left'; 65 | public static readonly Right = 'right'; 66 | public static readonly Top = 'top'; 67 | public static readonly TopLeft = 'top-left'; 68 | public static readonly TopRight = 'top-right'; 69 | } 70 | 71 | export enum ProgressIndicatorLocation { 72 | InsideNextButton = 'inside-next-button', 73 | TopOfTourBlock = 'top-of-tour-block', 74 | None = 'none', 75 | } 76 | -------------------------------------------------------------------------------- /projects/ngx-guided-tour/src/lib/guided-tour.module.ts: -------------------------------------------------------------------------------- 1 | import { GuidedTourService } from './guided-tour.service'; 2 | import { GuidedTourComponent } from './guided-tour.component'; 3 | import { NgModule, ErrorHandler, ModuleWithProviders } from '@angular/core'; 4 | import { CommonModule } from '@angular/common'; 5 | import { WindowRefService } from './windowref.service'; 6 | 7 | @NgModule({ 8 | declarations: [GuidedTourComponent], 9 | imports: [CommonModule], 10 | providers: [WindowRefService], 11 | exports: [GuidedTourComponent], 12 | entryComponents: [GuidedTourComponent], 13 | }) 14 | export class GuidedTourModule { 15 | public static forRoot(): ModuleWithProviders { 16 | return { 17 | ngModule: GuidedTourModule, 18 | providers: [ErrorHandler, GuidedTourService], 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /projects/ngx-guided-tour/src/lib/guided-tour.service.ts: -------------------------------------------------------------------------------- 1 | import { debounceTime } from 'rxjs/operators'; 2 | import { ErrorHandler, Inject, Injectable } from '@angular/core'; 3 | import { Observable, Subject, fromEvent } from 'rxjs'; 4 | import { GuidedTour, TourStep, Orientation, OrientationConfiguration } from './guided-tour.constants'; 5 | import { cloneDeep } from 'lodash'; 6 | import { DOCUMENT } from "@angular/common"; 7 | import { WindowRefService } from "./windowref.service"; 8 | 9 | @Injectable() 10 | export class GuidedTourService { 11 | public guidedTourCurrentStepStream: Observable; 12 | public guidedTourOrbShowingStream: Observable; 13 | 14 | private _guidedTourCurrentStepSubject = new Subject(); 15 | private _guidedTourOrbShowingSubject = new Subject(); 16 | private _currentTourStepIndex = 0; 17 | private _currentTour: GuidedTour = null; 18 | private _onFirstStep = true; 19 | private _onLastStep = true; 20 | private _onResizeMessage = false; 21 | 22 | constructor( 23 | public errorHandler: ErrorHandler, 24 | private windowRef: WindowRefService, 25 | @Inject(DOCUMENT) private dom 26 | ) { 27 | this.guidedTourCurrentStepStream = this._guidedTourCurrentStepSubject.asObservable(); 28 | this.guidedTourOrbShowingStream = this._guidedTourOrbShowingSubject.asObservable(); 29 | 30 | fromEvent(this.windowRef.nativeWindow, 'resize').pipe(debounceTime(200)).subscribe(() => { 31 | if (this._currentTour && this._currentTourStepIndex > -1) { 32 | if (this._currentTour.minimumScreenSize && this._currentTour.minimumScreenSize >= this.windowRef.nativeWindow.innerWidth) { 33 | this._onResizeMessage = true; 34 | const dialog = this._currentTour.resizeDialog || { 35 | title: 'Please resize', 36 | content: 'You have resized the tour to a size that is too small to continue. Please resize the browser to a larger size to continue the tour or close the tour.' 37 | }; 38 | 39 | this._guidedTourCurrentStepSubject.next(dialog); 40 | } else { 41 | this._onResizeMessage = false; 42 | this._guidedTourCurrentStepSubject.next(this.getPreparedTourStep(this._currentTourStepIndex)); 43 | } 44 | } 45 | }); 46 | } 47 | 48 | public nextStep(): void { 49 | if (this._currentTour.steps[this._currentTourStepIndex].closeAction) { 50 | this._currentTour.steps[this._currentTourStepIndex].closeAction(); 51 | } 52 | if (this._currentTour.steps[this._currentTourStepIndex + 1]) { 53 | this._currentTourStepIndex++; 54 | this._setFirstAndLast(); 55 | if (this._currentTour.steps[this._currentTourStepIndex].action) { 56 | this._currentTour.steps[this._currentTourStepIndex].action(); 57 | // Usually an action is opening something so we need to give it time to render. 58 | setTimeout(() => { 59 | if (this._checkSelectorValidity()) { 60 | this._guidedTourCurrentStepSubject.next(this.getPreparedTourStep(this._currentTourStepIndex)); 61 | } else { 62 | this.nextStep(); 63 | } 64 | }); 65 | } else { 66 | if (this._checkSelectorValidity()) { 67 | this._guidedTourCurrentStepSubject.next(this.getPreparedTourStep(this._currentTourStepIndex)); 68 | } else { 69 | this.nextStep(); 70 | } 71 | } 72 | } else { 73 | if (this._currentTour.completeCallback) { 74 | this._currentTour.completeCallback(); 75 | } 76 | this.resetTour(); 77 | } 78 | } 79 | 80 | public backStep(): void { 81 | if (this._currentTour.steps[this._currentTourStepIndex].closeAction) { 82 | this._currentTour.steps[this._currentTourStepIndex].closeAction(); 83 | } 84 | if (this._currentTour.steps[this._currentTourStepIndex - 1]) { 85 | this._currentTourStepIndex--; 86 | this._setFirstAndLast(); 87 | if (this._currentTour.steps[this._currentTourStepIndex].action) { 88 | this._currentTour.steps[this._currentTourStepIndex].action(); 89 | setTimeout(() => { 90 | if (this._checkSelectorValidity()) { 91 | this._guidedTourCurrentStepSubject.next(this.getPreparedTourStep(this._currentTourStepIndex)); 92 | } else { 93 | this.backStep(); 94 | } 95 | }); 96 | } else { 97 | if (this._checkSelectorValidity()) { 98 | this._guidedTourCurrentStepSubject.next(this.getPreparedTourStep(this._currentTourStepIndex)); 99 | } else { 100 | this.backStep(); 101 | } 102 | } 103 | } else { 104 | this.resetTour(); 105 | } 106 | } 107 | 108 | public skipTour(): void { 109 | if (this._currentTour.skipCallback) { 110 | this._currentTour.skipCallback(this._currentTourStepIndex); 111 | } 112 | this.resetTour(); 113 | } 114 | 115 | public resetTour(): void { 116 | this.dom.body.classList.remove('tour-open'); 117 | this._currentTour = null; 118 | this._currentTourStepIndex = 0; 119 | this._guidedTourCurrentStepSubject.next(null); 120 | } 121 | 122 | public startTour(tour: GuidedTour): void { 123 | this._currentTour = cloneDeep(tour); 124 | this._currentTour.steps = this._currentTour.steps.filter(step => !step.skipStep); 125 | this._currentTourStepIndex = 0; 126 | this._setFirstAndLast(); 127 | this._guidedTourOrbShowingSubject.next(this._currentTour.useOrb); 128 | if ( 129 | this._currentTour.steps.length > 0 130 | && (!this._currentTour.minimumScreenSize 131 | || (this.windowRef.nativeWindow.innerWidth >= this._currentTour.minimumScreenSize)) 132 | ) { 133 | if (!this._currentTour.useOrb) { 134 | this.dom.body.classList.add('tour-open'); 135 | } 136 | if (this._currentTour.steps[this._currentTourStepIndex].action) { 137 | this._currentTour.steps[this._currentTourStepIndex].action(); 138 | } 139 | if (this._checkSelectorValidity()) { 140 | this._guidedTourCurrentStepSubject.next(this.getPreparedTourStep(this._currentTourStepIndex)); 141 | } else { 142 | this.nextStep(); 143 | } 144 | } 145 | } 146 | 147 | public activateOrb(): void { 148 | this._guidedTourOrbShowingSubject.next(false); 149 | this.dom.body.classList.add('tour-open'); 150 | } 151 | 152 | private _setFirstAndLast(): void { 153 | this._onLastStep = (this._currentTour.steps.length - 1) === this._currentTourStepIndex; 154 | this._onFirstStep = this._currentTourStepIndex === 0; 155 | } 156 | 157 | private _checkSelectorValidity(): boolean { 158 | if (this._currentTour.steps[this._currentTourStepIndex].selector) { 159 | const selectedElement = this.dom.querySelector(this._currentTour.steps[this._currentTourStepIndex].selector); 160 | if (!selectedElement) { 161 | this.errorHandler.handleError( 162 | // If error handler is configured this should not block the browser. 163 | new Error(`Error finding selector ${this._currentTour.steps[this._currentTourStepIndex].selector} on step ${this._currentTourStepIndex + 1} during guided tour: ${this._currentTour.tourId}`) 164 | ); 165 | return false; 166 | } 167 | } 168 | return true; 169 | } 170 | 171 | public get onLastStep(): boolean { 172 | return this._onLastStep; 173 | } 174 | 175 | public get onFirstStep(): boolean { 176 | return this._onFirstStep; 177 | } 178 | 179 | public get onResizeMessage(): boolean { 180 | return this._onResizeMessage; 181 | } 182 | 183 | public get currentTourStepDisplay(): number { 184 | return this._currentTourStepIndex + 1; 185 | } 186 | 187 | public get currentTourStepCount(): number { 188 | return this._currentTour && this._currentTour.steps ? this._currentTour.steps.length : 0; 189 | } 190 | 191 | public get preventBackdropFromAdvancing(): boolean { 192 | return this._currentTour && this._currentTour.preventBackdropFromAdvancing; 193 | } 194 | 195 | private getPreparedTourStep(index: number): TourStep { 196 | return this.setTourOrientation(this._currentTour.steps[index]); 197 | } 198 | 199 | private setTourOrientation(step: TourStep): TourStep { 200 | const convertedStep = cloneDeep(step); 201 | if ( 202 | convertedStep.orientation 203 | && !(typeof convertedStep.orientation === 'string') 204 | && (convertedStep.orientation as OrientationConfiguration[]).length 205 | ) { 206 | (convertedStep.orientation as OrientationConfiguration[]).sort((a: OrientationConfiguration, b: OrientationConfiguration) => { 207 | if (!b.maximumSize) { 208 | return 1; 209 | } 210 | if (!a.maximumSize) { 211 | return -1; 212 | } 213 | return b.maximumSize - a.maximumSize; 214 | }); 215 | 216 | let currentOrientation: Orientation = Orientation.Top; 217 | (convertedStep.orientation as OrientationConfiguration[]).forEach( 218 | (orientationConfig: OrientationConfiguration) => { 219 | if (!orientationConfig.maximumSize || this.windowRef.nativeWindow.innerWidth <= orientationConfig.maximumSize) { 220 | currentOrientation = orientationConfig.orientationDirection; 221 | } 222 | } 223 | ); 224 | 225 | convertedStep.orientation = currentOrientation; 226 | } 227 | return convertedStep; 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /projects/ngx-guided-tour/src/lib/ngx-guided-tour.module.ts: -------------------------------------------------------------------------------- 1 | import { GuidedTourService } from './guided-tour.service'; 2 | import { GuidedTourComponent } from './guided-tour.component'; 3 | import { NgModule, ErrorHandler, ModuleWithProviders } from '@angular/core'; 4 | import { CommonModule } from '@angular/common'; 5 | 6 | @NgModule({ 7 | declarations: [ 8 | GuidedTourComponent 9 | ], 10 | imports: [ 11 | CommonModule 12 | ], 13 | exports: [ 14 | GuidedTourComponent 15 | ], 16 | entryComponents: [ 17 | GuidedTourComponent 18 | ] 19 | }) 20 | export class NgxGuidedTourModule { 21 | public static forRoot(): ModuleWithProviders { 22 | return { 23 | ngModule: NgxGuidedTourModule, 24 | providers: [ 25 | ErrorHandler, 26 | GuidedTourService 27 | ] 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /projects/ngx-guided-tour/src/lib/windowref.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable, PLATFORM_ID } from "@angular/core"; 2 | import { isPlatformBrowser } from "@angular/common"; 3 | 4 | function getWindow(): any { 5 | return window; 6 | } 7 | 8 | function getMockWindow(): any { 9 | return { 10 | innerWidth: 0, 11 | innerHeight: 0, 12 | scrollY: 0, 13 | scrollX: 0, 14 | pageYOffset: 0, 15 | pageXOffset: 0, 16 | scroll: () => {}, 17 | scrollTo: () => {}, 18 | addEventListener: () => {}, 19 | removeEventListener: () => {}, 20 | } 21 | } 22 | 23 | @Injectable() 24 | export class WindowRefService { 25 | private readonly isBrowser: boolean = false; 26 | 27 | get nativeWindow(): any { 28 | if (this.isBrowser) { 29 | return getWindow(); 30 | } else { 31 | return getMockWindow(); 32 | } 33 | } 34 | 35 | constructor(@Inject(PLATFORM_ID) platformId) { 36 | this.isBrowser = isPlatformBrowser(platformId); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /projects/ngx-guided-tour/src/public_api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of ngx-guided-tour 3 | */ 4 | 5 | export * from './lib/guided-tour.module'; 6 | export * from './lib/guided-tour.component'; 7 | export * from './lib/guided-tour.service'; 8 | export * from './lib/guided-tour.constants'; 9 | export * from './lib/windowref.service'; 10 | -------------------------------------------------------------------------------- /projects/ngx-guided-tour/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'core-js/es7/reflect'; 4 | import 'zone.js/dist/zone'; 5 | import 'zone.js/dist/zone-testing'; 6 | import { getTestBed } from '@angular/core/testing'; 7 | import { 8 | BrowserDynamicTestingModule, 9 | platformBrowserDynamicTesting 10 | } from '@angular/platform-browser-dynamic/testing'; 11 | 12 | declare const require: any; 13 | 14 | // First, initialize the Angular testing environment. 15 | getTestBed().initTestEnvironment( 16 | BrowserDynamicTestingModule, 17 | platformBrowserDynamicTesting() 18 | ); 19 | // Then we find all the tests. 20 | const context = require.context('./', true, /\.spec\.ts$/); 21 | // And load the modules. 22 | context.keys().map(context); 23 | -------------------------------------------------------------------------------- /projects/ngx-guided-tour/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "es2015", 6 | "module": "es2015", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "sourceMap": true, 10 | "inlineSources": true, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "importHelpers": true, 14 | "types": [], 15 | "lib": [ 16 | "dom", 17 | "es2018" 18 | ] 19 | }, 20 | "angularCompilerOptions": { 21 | "annotateForClosureCompiler": true, 22 | "skipTemplateCodegen": true, 23 | "strictMetadataEmit": true, 24 | "fullTemplateTypeCheck": true, 25 | "strictInjectionParameters": true, 26 | "enableResourceInlining": true 27 | }, 28 | "exclude": [ 29 | "src/test.ts", 30 | "**/*.spec.ts" 31 | ] 32 | } -------------------------------------------------------------------------------- /projects/ngx-guided-tour/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } -------------------------------------------------------------------------------- /projects/ngx-guided-tour/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "lib", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "lib", 14 | "kebab-case" 15 | ] 16 | } 17 | } -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .jumbotron img { 2 | vertical-align: bottom; 3 | margin-right: 10px; 4 | } 5 | 6 | .center-content { 7 | width: 100%; 8 | max-width: 800px; 9 | margin: auto; 10 | padding: 0 15px; 11 | display: flex; 12 | flex-direction: row; 13 | align-items: center; 14 | justify-content: center; 15 | flex-wrap: wrap; 16 | text-align: center; 17 | } 18 | 19 | .center-content img { 20 | margin: 30px; 21 | cursor: pointer; 22 | } 23 | 24 | p { 25 | margin: 0; 26 | } 27 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | ngx-guided-tour Demo 5 |

6 |
7 | 8 |
9 |
10 | Guided tour is a great way to introduce your users to new features or remind them how to use exisiting features. Hover your mouse over the orb above to start the tour. 11 |
12 |
13 | Orbs are optional, If not set or set to false the tour will just start. 14 |
15 |
16 | 17 |
18 |
19 | 20 |
21 | 24 |
25 | 26 |
27 |
28 | 29 |
30 | 31 | Click restart to start the tour again. 32 |
33 |
34 | There are multiple features to correctly position and caclulate scrolling on your tours. 35 |
36 |
37 | 38 |

39 |
40 | 41 |
42 | 43 |

44 |
45 | 46 |
47 | 48 | This content is on the bottom of the page so that the tour is forced to scroll to it. 49 | 50 |
51 | 52 | 53 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | import { AppModule } from './app.module'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(waitForAsync(() => { 7 | TestBed.configureTestingModule({ 8 | imports: [ 9 | AppModule, 10 | ], 11 | }).compileComponents(); 12 | })); 13 | 14 | it('should create the app', async () => { 15 | const fixture = TestBed.createComponent(AppComponent); 16 | const app = fixture.debugElement.componentInstance; 17 | expect(app).toBeTruthy(); 18 | }); 19 | 20 | 21 | 22 | it(`should have as title 'ngx-guided-tour-demo'`, () => { 23 | const fixture = TestBed.createComponent(AppComponent); 24 | const app = fixture.debugElement.componentInstance; 25 | console.log('App', app); 26 | expect(app.title).toEqual('ngx-guided-tour-demo'); 27 | }); 28 | 29 | it('should render title in a h1 tag', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.debugElement.nativeElement; 33 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to ngx-guided-tour-demo!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { GuidedTour, Orientation } from 'projects/ngx-guided-tour/src/lib/guided-tour.constants'; 3 | import { GuidedTourService } from 'projects/ngx-guided-tour/src/lib/guided-tour.service'; 4 | 5 | @Component({ 6 | selector: 'app-root', 7 | templateUrl: './app.component.html', 8 | styleUrls: ['./app.component.css'] 9 | }) 10 | export class AppComponent { 11 | title = 'ngx-guided-tour-demo'; 12 | 13 | public dashboardTour: GuidedTour = { 14 | tourId: 'purchases-tour', 15 | useOrb: true, 16 | steps: [ 17 | { 18 | title: 'Welcome to the Guided Tour Demo', 19 | selector: '.demo-title', 20 | content: 'Step 1', 21 | orientation: Orientation.Bottom 22 | }, 23 | { 24 | title: 'General page step', 25 | content: 'We have the concept of general page steps so that a you can introuce a user to a page or non specific instructions', 26 | }, 27 | { 28 | title: 'Positioning', 29 | selector: '.tour-middle-content', 30 | content: 'Step position can be set so that steps are always in view. This step is on the left.', 31 | orientation: Orientation.Left 32 | }, 33 | { 34 | title: 'Positioning 2', 35 | selector: '.tour-middle-content', 36 | content: 'This step is on the right.', 37 | orientation: Orientation.Right 38 | }, 39 | { 40 | title: 'Scroll to content', 41 | selector: '.tour-scroll', 42 | content: 'Automatically scroll to elements so they are in view', 43 | orientation: Orientation.Top 44 | } 45 | ] 46 | }; 47 | 48 | constructor( 49 | private guidedTourService: GuidedTourService 50 | ) { 51 | 52 | setTimeout(() => { 53 | this.guidedTourService.startTour(this.dashboardTour); 54 | }, 1000); 55 | } 56 | 57 | public restartTour(): void { 58 | this.guidedTourService.startTour(this.dashboardTour); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { GuidedTourModule } from 'projects/ngx-guided-tour/src/lib/guided-tour.module'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { NgModule } from '@angular/core'; 4 | 5 | import { AppComponent } from './app.component'; 6 | 7 | @NgModule({ 8 | declarations: [ 9 | AppComponent 10 | ], 11 | imports: [ 12 | BrowserModule, 13 | GuidedTourModule.forRoot() 14 | ], 15 | providers: [], 16 | bootstrap: [AppComponent] 17 | }) 18 | export class AppModule { } 19 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsqlabs/ngx-guided-tour/0d75fc932c76a7d41a0b90b19c74fce73b110d8f/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/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 -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsqlabs/ngx-guided-tour/0d75fc932c76a7d41a0b90b19c74fce73b110d8f/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | NgxGuidedTourDemo 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/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'), 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 | }; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills. 22 | * This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot 23 | */ 24 | 25 | // import 'core-js/es6/symbol'; 26 | // import 'core-js/es6/object'; 27 | // import 'core-js/es6/function'; 28 | // import 'core-js/es6/parse-int'; 29 | // import 'core-js/es6/parse-float'; 30 | // import 'core-js/es6/number'; 31 | // import 'core-js/es6/math'; 32 | // import 'core-js/es6/string'; 33 | // import 'core-js/es6/date'; 34 | // import 'core-js/es6/array'; 35 | // import 'core-js/es6/regexp'; 36 | // import 'core-js/es6/map'; 37 | // import 'core-js/es6/weak-map'; 38 | // import 'core-js/es6/set'; 39 | 40 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 41 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 42 | 43 | /** IE10 and IE11 requires the following for the Reflect API. */ 44 | // import 'core-js/es6/reflect'; 45 | 46 | /** 47 | * Web Animations `@angular/platform-browser/animations` 48 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 49 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 50 | */ 51 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 52 | 53 | /** 54 | * By default, zone.js will patch all possible macroTask and DomEvents 55 | * user can disable parts of macroTask/DomEvents patch by setting following flags 56 | * because those flags need to be set before `zone.js` being loaded, and webpack 57 | * will put import in the top of bundle, so user need to create a separate file 58 | * in this directory (for example: zone-flags.ts), and put the following flags 59 | * into that file, and then add the following code before importing zone.js. 60 | * import './zone-flags.ts'; 61 | * 62 | * The flags allowed in zone-flags.ts are listed here. 63 | * 64 | * The following flags will work for all browsers. 65 | * 66 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 67 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 68 | * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 69 | * 70 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 71 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 72 | * 73 | * (window as any).__Zone_enable_cross_context_check = true; 74 | * 75 | */ 76 | 77 | /*************************************************************************************************** 78 | * Zone JS is required by default for Angular itself. 79 | */ 80 | import 'zone.js/dist/zone'; // Included with Angular CLI. 81 | 82 | 83 | /*************************************************************************************************** 84 | * APPLICATION IMPORTS 85 | */ 86 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import 'bootstrap/dist/css/bootstrap.min.css'; 3 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } -------------------------------------------------------------------------------- /src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2020", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "es2020", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ], 21 | "paths": { 22 | "ngx-guided-tour": [ 23 | "dist/ngx-guided-tour" 24 | ], 25 | "ngx-guided-tour/*": [ 26 | "dist/ngx-guided-tour/*" 27 | ] 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint-eslint-rules" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "eofline": true, 15 | "forin": false, 16 | "import-blacklist": false, 17 | "import-spacing": true, 18 | "indent": [ 19 | true, 20 | "spaces", 21 | 4 22 | ], 23 | "interface-over-type-literal": true, 24 | "label-position": true, 25 | "max-line-length": false, 26 | "member-access": false, 27 | "member-ordering": [ 28 | true, 29 | { 30 | "order": [ 31 | "static-field", 32 | "instance-field", 33 | "static-method", 34 | "instance-method" 35 | ] 36 | } 37 | ], 38 | "no-arg": true, 39 | "no-bitwise": true, 40 | "no-console": [ 41 | true, 42 | "debug", 43 | "info", 44 | "time", 45 | "timeEnd", 46 | "trace" 47 | ], 48 | "no-construct": true, 49 | "no-debugger": true, 50 | "no-duplicate-super": true, 51 | "no-empty": false, 52 | "no-empty-interface": true, 53 | "no-eval": true, 54 | "no-inferrable-types": [ 55 | true, 56 | "ignore-params" 57 | ], 58 | "no-misused-new": true, 59 | "no-non-null-assertion": true, 60 | "no-shadowed-variable": true, 61 | "no-string-literal": false, 62 | "no-string-throw": true, 63 | "no-switch-case-fall-through": true, 64 | "no-trailing-whitespace": true, 65 | "no-unnecessary-initializer": true, 66 | "no-unused-expression": true, 67 | "no-unused-variable": true, 68 | "no-use-before-declare": true, 69 | "no-var-keyword": true, 70 | "object-curly-spacing": [ 71 | true, 72 | "always" 73 | ], 74 | "object-literal-sort-keys": false, 75 | "one-line": [ 76 | true, 77 | "check-open-brace" 78 | ], 79 | "ordered-imports": true, 80 | "prefer-const": true, 81 | "quotemark": [ 82 | true, 83 | "single" 84 | ], 85 | "radix": true, 86 | "semicolon": [ 87 | true, 88 | "always" 89 | ], 90 | "triple-equals": [ 91 | true, 92 | "allow-null-check" 93 | ], 94 | "typedef": [ 95 | true, 96 | { 97 | "call-signature": true, 98 | "arrow-call-signature": false, 99 | "parameter": true, 100 | "arrow-parameter": false, 101 | "property-declaration": true, 102 | "variable-declaration": true, 103 | "member-variable-declaration": true, 104 | "object-destructuring": false, 105 | "array-destructuring": false 106 | } 107 | ], 108 | "typedef-whitespace": [ 109 | true, 110 | { 111 | "call-signature": "nospace", 112 | "index-signature": "nospace", 113 | "parameter": "nospace", 114 | "property-declaration": "nospace", 115 | "variable-declaration": "nospace" 116 | }, 117 | { 118 | "call-signature": "onespace", 119 | "index-signature": "onespace", 120 | "parameter": "onespace", 121 | "property-declaration": "onespace", 122 | "variable-declaration": "onespace" 123 | } 124 | ], 125 | "typeof-compare": true, 126 | "unified-signatures": true, 127 | "variable-name": false, 128 | "whitespace": [ 129 | true, 130 | "check-branch", 131 | "check-decl", 132 | "check-operator", 133 | "check-separator", 134 | "check-type" 135 | ], 136 | "directive-selector": false, 137 | "component-selector": false, 138 | "use-input-property-decorator": true, 139 | "use-output-property-decorator": true, 140 | "use-host-property-decorator": true, 141 | "no-input-rename": true, 142 | "no-output-rename": true, 143 | "use-life-cycle-interface": true, 144 | "use-pipe-transform-interface": true, 145 | "component-class-suffix": true, 146 | "directive-class-suffix": true, 147 | "no-access-missing-member": true, 148 | "templates-use-public": true, 149 | "invoke-injectable": true 150 | } 151 | } 152 | --------------------------------------------------------------------------------