├── .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 |
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 |
--------------------------------------------------------------------------------