4 |
5 |
6 | Dummy
7 |
8 |
9 |
10 | {{content-for "head"}}
11 |
12 |
13 |
14 |
15 | {{content-for "head-footer"}}
16 |
17 |
18 | {{content-for "body"}}
19 |
20 |
21 |
22 |
23 | {{content-for "body-footer"}}
24 |
25 |
26 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How To Contribute
2 |
3 | ## Installation
4 |
5 | * `git clone `
6 | * `cd ember-router-scroll`
7 | * `yarn install`
8 |
9 | ## Linting
10 |
11 | * `yarn lint`
12 | * `yarn lint:fix`
13 |
14 | ## Running tests
15 |
16 | * `ember test` – Runs the test suite on the current Ember version
17 | * `ember test --server` – Runs the test suite in "watch mode"
18 | * `ember try:each` – Runs the test suite against multiple Ember versions
19 |
20 | ## Automation
21 |
22 | * `git checkout `
23 | * `nvm i`
24 | * `yarn`
25 | * `yarn update`
26 |
27 | ## Running the dummy application
28 |
29 | * `ember serve`
30 | * Visit the dummy application at [http://localhost:4200](http://localhost:4200).
31 |
32 | For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/).
33 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Dollar Shave Club
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/target.hbs:
--------------------------------------------------------------------------------
1 |
4 | Orbital velocity achieved!
5 | Ember-router-scroll has taken you to the top of the page!
6 |
7 |
8 | Clicking the submarine will take you to the top of the previous page.
9 | Using the back button will take you to your previous scroll position.
10 |
11 |
7 | Orbital velocity achieved!
8 | Ember-router-scroll has taken you to the top of the page!
9 |
10 |
11 | Clicking the submarine will take you to the top of the previous page.
12 | Using the back button will take you to your previous scroll position.
13 |
14 |
15 |
16 |
17 | — Orbit -500 km
18 |
19 |
20 | — Orbit -1000 km
21 |
22 |
23 | — Orbit -1500 km
24 |
25 |
26 | — Orbit -2000 km
27 |
28 |
29 | — Orbit -2500 km
30 |
31 |
32 | — Orbit -3000 km
33 |
34 |
35 | — Orbit -3500 km
36 |
37 |
38 | — Orbit -4000 km
39 |
40 |
41 | — Orbit -4500 km
42 |
43 |
44 | — Orbit -5000 km
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/tests/dummy/config/environment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (environment) {
4 | let ENV = {
5 | modulePrefix: 'dummy',
6 | environment,
7 | rootURL: '/',
8 | locationType: 'auto',
9 | historySupportMiddleware: true,
10 | EmberENV: {
11 | FEATURES: {
12 | // Here you can enable experimental features on an ember canary build
13 | // e.g. 'with-controller': true
14 | },
15 | EXTEND_PROTOTYPES: {
16 | // Prevent Ember Data from overriding Date.parse.
17 | Date: false,
18 | },
19 | },
20 |
21 | // routerScroll: {
22 | // targetElement: '#target-main'
23 | // },
24 |
25 | APP: {
26 | // Here you can pass flags/options to your application instance
27 | // when it is created
28 | },
29 | };
30 |
31 | if (environment === 'development') {
32 | // ENV.APP.LOG_RESOLVER = true;
33 | // ENV.APP.LOG_ACTIVE_GENERATION = true;
34 | // ENV.APP.LOG_TRANSITIONS = true;
35 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
36 | // ENV.APP.LOG_VIEW_LOOKUPS = true;
37 | }
38 |
39 | if (environment === 'test') {
40 | // Testem prefers this...
41 | ENV.locationType = 'none';
42 |
43 | // keep test console output quieter
44 | ENV.APP.LOG_ACTIVE_GENERATION = false;
45 | ENV.APP.LOG_VIEW_LOOKUPS = false;
46 |
47 | ENV.APP.rootElement = '#ember-testing';
48 | ENV.APP.autoboot = false;
49 | }
50 |
51 | if (environment === 'production') {
52 | ENV.rootURL = '/ember-router-scroll/';
53 | }
54 |
55 | return ENV;
56 | };
57 |
--------------------------------------------------------------------------------
/tests/dummy/app/styles/app.css:
--------------------------------------------------------------------------------
1 | @import url(https://fonts.googleapis.com/css?family=VT323);
2 |
3 | body {
4 | margin: 0;
5 | }
6 |
7 | #death-star {
8 | position:absolute;
9 | margin: 1550px auto 0 60%;
10 | width:75px;
11 | }
12 |
13 | #monster {
14 | bottom:0;
15 | position: absolute;
16 | margin: 0 auto 0 60%;
17 | width: 175px;
18 | }
19 |
20 | #ocean {
21 | background: linear-gradient(to top, black, #000055, #00DCFF);
22 | position: absolute;
23 | width:100%;
24 | height:3960px;
25 | }
26 |
27 | #space {
28 | background: linear-gradient(to top, #00DCFF, #000055, #000022, black);
29 | position: absolute;
30 | width:100%;
31 | height:3960px;
32 | }
33 |
34 | #stars {
35 | background: url(../images/stars.png) repeat;
36 | position: absolute;
37 | width:100%;
38 | height:3600px;
39 | }
40 |
41 | #submarine {
42 | display:block;
43 | position:fixed;
44 | top:50%;
45 | right:33%;
46 | width:150px;
47 | }
48 |
49 | div {
50 | color:white;
51 | font-family:vt323;
52 | }
53 |
54 | #intro {
55 | font-size: 32px;
56 | margin: 12% 10% -200px 10%;
57 | }
58 |
59 | #change-size {
60 | bottom: 0;
61 | color: white;
62 | padding: 16px;
63 | position: fixed;
64 | right: 0;
65 | }
66 |
67 | .small {
68 | transform: scale(0.5);
69 | }
70 |
71 | #nav {
72 | height: 44px;
73 | background-color: white;
74 | color: black;
75 | }
76 |
77 | .depth {
78 | font-size: 26px;
79 | }
80 |
81 | .flip {
82 | -moz-transform: scaleX(-1);
83 | -o-transform: scaleX(-1);
84 | -webkit-transform: scaleX(-1);
85 | transform: scaleX(-1);
86 | filter: FlipH;
87 | -ms-filter: "FlipH";
88 | left:33%;
89 | }
90 |
91 | .small {
92 | color: #dddddd;
93 | font-size: 22px;
94 | }
95 |
96 | .left {
97 | margin: 300px 0 0 10%;
98 | }
99 | .right {
100 | margin: 300px 0 0 75%;
101 | }
102 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - master
8 | pull_request: {}
9 |
10 | jobs:
11 | test:
12 | name: "Tests"
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v2
17 | - name: Install Node
18 | uses: actions/setup-node@v2
19 | with:
20 | node-version: 12.x
21 | cache: yarn
22 | - name: Install Dependencies
23 | run: yarn install --frozen-lockfile
24 | - name: Lint
25 | run: yarn lint
26 | - name: Run Tests
27 | run: yarn test:ember
28 |
29 | floating:
30 | name: "Floating Dependencies"
31 | runs-on: ubuntu-latest
32 |
33 | steps:
34 | - uses: actions/checkout@v2
35 | - uses: actions/setup-node@v2
36 | with:
37 | node-version: 12.x
38 | cache: yarn
39 | - name: Install Dependencies
40 | run: yarn install --no-lockfile
41 | - name: Run Tests
42 | run: yarn test:ember
43 |
44 | try-scenarios:
45 | name: ${{ matrix.try-scenario }}
46 | runs-on: ubuntu-latest
47 | needs: 'test'
48 |
49 | strategy:
50 | fail-fast: false
51 | matrix:
52 | try-scenario:
53 | - ember-lts-3.12
54 | - ember-lts-3.16
55 | - ember-lts-3.20
56 | - ember-lts-3.24
57 | - ember-release
58 | - ember-beta
59 | - ember-canary
60 | - ember-classic
61 | - ember-default-with-jquery
62 | - embroider-safe
63 | - embroider-optimized
64 |
65 | steps:
66 | - uses: actions/checkout@v2
67 | - name: Install Node
68 | uses: actions/setup-node@v2
69 | with:
70 | node-version: 12.x
71 | cache: yarn
72 | - name: Install Dependencies
73 | run: yarn install --frozen-lockfile
74 | - name: Run Tests
75 | run: ./node_modules/.bin/ember try:one ${{ matrix.try-scenario }}
76 |
--------------------------------------------------------------------------------
/tests/acceptance/basic-functionality-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from 'qunit';
2 | import { setupApplicationTest } from 'ember-qunit';
3 | import { visit, click, currentURL } from '@ember/test-helpers';
4 | import config from 'dummy/config/environment';
5 |
6 | module('Acceptance | basic functionality', function (hooks) {
7 | setupApplicationTest(hooks);
8 |
9 | hooks.beforeEach(function () {
10 | document.getElementById('ember-testing-container').scrollTop = 0;
11 | });
12 |
13 | hooks.afterEach(function () {
14 | config['routerScroll'] = {};
15 | });
16 |
17 | test('The application should work when loading a page and clicking a link', async function (assert) {
18 | config['routerScroll'] = {
19 | scrollElement: '#ember-testing-container',
20 | };
21 |
22 | await visit('/');
23 |
24 | // testing specific
25 | let container = document.getElementById('ember-testing-container');
26 | assert.strictEqual(container.scrollTop, 0);
27 |
28 | document.getElementById('monster').scrollIntoView(false);
29 | assert.ok(container.scrollTop > 0);
30 |
31 | await click('a[href="/next-page"]');
32 | assert.strictEqual(currentURL(), '/next-page');
33 | });
34 |
35 | test('The application should work when loading a page and clicking a link to target an element to scroll to', async function (assert) {
36 | config['routerScroll'] = {
37 | scrollElement: '#target-main',
38 | };
39 |
40 | await visit('/target');
41 |
42 | // testing specific
43 | let container = document.getElementById('ember-testing-container');
44 | assert.strictEqual(container.scrollTop, 0);
45 |
46 | document.getElementById('monster').scrollIntoView(false);
47 | assert.ok(container.scrollTop > 0);
48 |
49 | await click('a[href="/target-next-page"]');
50 | assert.strictEqual(currentURL(), '/target-next-page');
51 | });
52 |
53 | test('The application should work when just changing query params', async function (assert) {
54 | config['routerScroll'] = {
55 | scrollElement: '#ember-testing-container',
56 | };
57 |
58 | await visit('/');
59 |
60 | let container = document.getElementById('ember-testing-container');
61 | assert.strictEqual(container.scrollTop, 0);
62 |
63 | document.getElementById('monster').scrollIntoView(false);
64 | assert.ok(container.scrollTop > 0);
65 |
66 | await click('#change-size');
67 | assert.ok(container.scrollTop > 0);
68 | });
69 | });
70 |
--------------------------------------------------------------------------------
/config/ember-try.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const getChannelURL = require('ember-source-channel-url');
4 | const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup');
5 |
6 | module.exports = async function () {
7 | return {
8 | useYarn: true,
9 | scenarios: [
10 | {
11 | name: 'ember-lts-3.12',
12 | npm: {
13 | devDependencies: {
14 | 'ember-source': '~3.12.0',
15 | },
16 | },
17 | },
18 | {
19 | name: 'ember-lts-3.16',
20 | npm: {
21 | devDependencies: {
22 | 'ember-source': '~3.16.0',
23 | },
24 | },
25 | },
26 | {
27 | name: 'ember-lts-3.20',
28 | npm: {
29 | devDependencies: {
30 | 'ember-source': '~3.20.5',
31 | },
32 | },
33 | },
34 | {
35 | name: 'ember-lts-3.24',
36 | npm: {
37 | devDependencies: {
38 | 'ember-source': '~3.24.3',
39 | },
40 | },
41 | },
42 | {
43 | name: 'ember-release',
44 | npm: {
45 | devDependencies: {
46 | 'ember-source': await getChannelURL('release'),
47 | },
48 | },
49 | },
50 | {
51 | name: 'ember-beta',
52 | npm: {
53 | devDependencies: {
54 | 'ember-source': await getChannelURL('beta'),
55 | },
56 | },
57 | },
58 | {
59 | name: 'ember-canary',
60 | npm: {
61 | devDependencies: {
62 | 'ember-source': await getChannelURL('canary'),
63 | },
64 | },
65 | },
66 | {
67 | name: 'ember-default-with-jquery',
68 | env: {
69 | EMBER_OPTIONAL_FEATURES: JSON.stringify({
70 | 'jquery-integration': true,
71 | }),
72 | },
73 | npm: {
74 | devDependencies: {
75 | '@ember/jquery': '^1.1.0',
76 | },
77 | },
78 | },
79 | {
80 | name: 'ember-classic',
81 | env: {
82 | EMBER_OPTIONAL_FEATURES: JSON.stringify({
83 | 'application-template-wrapper': true,
84 | 'default-async-observers': false,
85 | 'template-only-glimmer-components': false,
86 | }),
87 | },
88 | npm: {
89 | devDependencies: {
90 | 'ember-source': '~3.28.0',
91 | },
92 | ember: {
93 | edition: 'classic',
94 | },
95 | },
96 | },
97 | embroiderSafe(),
98 | embroiderOptimized(),
99 | ],
100 | };
101 | };
102 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | education, socio-economic status, nationality, personal appearance, race,
10 | religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-router-scroll",
3 | "version": "4.1.2",
4 | "description": "Scroll to top with preserved browser history scroll position",
5 | "keywords": [
6 | "ember-addon",
7 | "ember-router-scroll",
8 | "popstateEvent",
9 | "browser scroll"
10 | ],
11 | "license": "MIT",
12 | "author": "Jason Farmer and Benny C. Wong ",
13 | "contributors": [
14 | {
15 | "name": "Brian Cardarella",
16 | "url": "https://github.com/bcardarella"
17 | },
18 | {
19 | "name": "Brian Gonzalez",
20 | "url": "https://github.com/briangonzalez"
21 | },
22 | {
23 | "name": "Robert Wagner",
24 | "url": "https://github.com/rwwagner90"
25 | },
26 | {
27 | "name": "Scott Newcomer",
28 | "url": "https://github.com/snewcomer"
29 | }
30 | ],
31 | "directories": {
32 | "doc": "doc",
33 | "test": "tests"
34 | },
35 | "repository": {
36 | "type": "git",
37 | "url": "https://github.com/dockyard/ember-router-scroll.git"
38 | },
39 | "bugs": {
40 | "url": "https://github.com/dockyard/ember-router-scroll/issues"
41 | },
42 | "homepage": "https://github.com/DockYard/ember-router-scroll",
43 | "scripts": {
44 | "build": "ember build --environment=production",
45 | "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"",
46 | "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix",
47 | "lint:hbs": "ember-template-lint .",
48 | "lint:hbs:fix": "ember-template-lint . --fix",
49 | "lint:js": "eslint . --cache",
50 | "lint:js:fix": "eslint . --fix",
51 | "start": "ember serve",
52 | "test": "npm-run-all lint test:*",
53 | "test:ember": "ember test",
54 | "test:ember-compatibility": "ember try:each",
55 | "deploy": "ember github-pages:commit --message \"Deploy gh-pages from commit $(git rev-parse HEAD)\"; git push; git checkout -",
56 | "postpublish": "git tag $npm_package_version && git push origin --tags",
57 | "update": "ember update --run-codemods"
58 | },
59 | "dependencies": {
60 | "ember-app-scheduler": "^5.1.2 || ^6.0.0 || ^7.0.0",
61 | "ember-cli-babel": "^7.26.6",
62 | "ember-compatibility-helpers": "^1.2.5"
63 | },
64 | "devDependencies": {
65 | "@ember/optional-features": "^2.0.0",
66 | "@ember/test-helpers": "^2.5.0",
67 | "@embroider/test-setup": "^0.47.0",
68 | "@glimmer/component": "^1.0.4",
69 | "@glimmer/tracking": "^1.0.4",
70 | "babel-eslint": "^10.1.0",
71 | "broccoli-asset-rev": "^3.0.0",
72 | "ember-auto-import": "^2.2.3",
73 | "ember-cli": "~3.28.3",
74 | "ember-cli-dependency-checker": "^3.2.0",
75 | "ember-cli-github-pages": "^0.2.0",
76 | "ember-cli-htmlbars": "^6.0.0",
77 | "ember-cli-inject-live-reload": "^2.1.0",
78 | "ember-cli-sri": "^2.1.1",
79 | "ember-cli-terser": "^4.0.2",
80 | "ember-decorators-polyfill": "^1.1.5",
81 | "ember-disable-prototype-extensions": "^1.1.3",
82 | "ember-export-application-global": "^2.0.1",
83 | "ember-load-initializers": "^2.1.2",
84 | "ember-maybe-import-regenerator": "^1.0.0",
85 | "ember-qunit": "^5.1.5",
86 | "ember-resolver": "^8.0.3",
87 | "ember-source": "~3.28.0",
88 | "ember-source-channel-url": "^3.0.0",
89 | "ember-template-lint": "^3.10.0",
90 | "ember-try": "^1.4.0",
91 | "eslint": "^7.32.0",
92 | "eslint-config-prettier": "^8.3.0",
93 | "eslint-plugin-ember": "^10.5.7",
94 | "eslint-plugin-node": "^11.1.0",
95 | "eslint-plugin-prettier": "^4.0.0",
96 | "eslint-plugin-qunit": "^7.0.0",
97 | "loader.js": "^4.7.0",
98 | "npm-run-all": "^4.1.5",
99 | "prettier": "^2.4.1",
100 | "qunit": "^2.17.2",
101 | "qunit-dom": "^2.0.0",
102 | "webpack": "^5.59.1"
103 | },
104 | "engines": {
105 | "node": "12.* || 14.* || >= 16"
106 | },
107 | "ember": {
108 | "edition": "octane"
109 | },
110 | "ember-addon": {
111 | "configPath": "tests/dummy/config",
112 | "demoURL": "https://dollarshaveclub.github.io/ember-router-scroll/"
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/tests/unit/services/router-scroll-test.js:
--------------------------------------------------------------------------------
1 | import EmberObject, { set } from '@ember/object';
2 | import { module, test } from 'qunit';
3 | import { setupTest } from 'ember-qunit';
4 |
5 | const defaultScrollMap = {
6 | default: {
7 | x: 0,
8 | y: 0,
9 | },
10 | };
11 |
12 | module('service:router-scroll', function (hooks) {
13 | setupTest(hooks);
14 |
15 | test('it inits `scrollMap` and `key`', function (assert) {
16 | const service = this.owner.lookup('service:router-scroll');
17 | assert.deepEqual(service.scrollMap, defaultScrollMap);
18 | assert.deepEqual(service.key, undefined);
19 | });
20 |
21 | test('it inits `scrollMap` and `key` with scrollElement other than window', function (assert) {
22 | const service = this.owner
23 | .factoryFor('service:router-scroll')
24 | .create({ scrollElement: '#other-elem' });
25 | assert.deepEqual(service.scrollMap, defaultScrollMap);
26 | assert.deepEqual(service.key, undefined);
27 | });
28 |
29 | test('updating will not set `scrollMap` to the current scroll position if `key` is not yet set', function (assert) {
30 | const service = this.owner.lookup('service:router-scroll');
31 |
32 | service.update();
33 | assert.deepEqual(service.scrollMap, defaultScrollMap);
34 | });
35 |
36 | test('updating will set `scrollMap` to the current scroll position', function (assert) {
37 | const service = this.owner
38 | .factoryFor('service:router-scroll')
39 | .create({ isFirstLoad: false });
40 |
41 | const expected = { x: window.scrollX, y: window.scrollY };
42 | set(service, 'key', '123');
43 | service.update();
44 | assert.deepEqual(service.scrollMap, {
45 | 123: expected,
46 | default: { x: 0, y: 0 },
47 | });
48 | });
49 |
50 | test('updating will not set `scrollMap` if scrollElement is defined and element is not found', function (assert) {
51 | const service = this.owner
52 | .factoryFor('service:router-scroll')
53 | .create({ scrollElement: '#other-elem' });
54 |
55 | service.update();
56 | const expected = { x: 0, y: 0 };
57 | assert.deepEqual(service.position, expected);
58 | assert.deepEqual(service.scrollMap, defaultScrollMap);
59 | });
60 |
61 | test('updating will not set `scrollMap` if targetElement is defined and element is not found', function (assert) {
62 | const service = this.owner
63 | .factoryFor('service:router-scroll')
64 | .create({ targetElement: '#other-elem' });
65 |
66 | service.update();
67 | const expected = { x: 0, y: 0 };
68 | assert.deepEqual(service.position, expected);
69 | assert.deepEqual(service.scrollMap, defaultScrollMap);
70 | });
71 |
72 | test('updating will not set `scrollMap` if scrollElement is defined and in fastboot', function (assert) {
73 | const otherElem = document.createElement('div');
74 | otherElem.setAttribute('id', 'other-elem');
75 | const testing = document.querySelector('#ember-testing');
76 | testing.appendChild(otherElem);
77 | this.owner.register(
78 | 'service:fastboot',
79 | class extends EmberObject {
80 | isFastBoot = true;
81 | }
82 | );
83 | const service = this.owner
84 | .factoryFor('service:router-scroll')
85 | .create({ scrollElement: '#other-elem' });
86 | window.history.replaceState({ uuid: '123' }, null);
87 |
88 | let expected = { x: 0, y: 0 };
89 | assert.deepEqual(service.position, expected, 'position is defaulted');
90 | service.update();
91 | assert.deepEqual(
92 | service.scrollMap,
93 | defaultScrollMap,
94 | 'does not set scrollMap b/c in fastboot'
95 | );
96 | });
97 |
98 | test('updating will not set `scrollMap` if targetElement is defined and in fastboot', function (assert) {
99 | const otherElem = document.createElement('div');
100 | otherElem.setAttribute('id', 'other-elem');
101 | const testing = document.querySelector('#ember-testing');
102 | testing.appendChild(otherElem);
103 | this.owner.register(
104 | 'service:fastboot',
105 | class extends EmberObject {
106 | isFastBoot = true;
107 | }
108 | );
109 | const service = this.owner
110 | .factoryFor('service:router-scroll')
111 | .create({ targetElement: '#other-elem' });
112 | window.history.replaceState({ uuid: '123' }, null);
113 |
114 | let expected = { x: 0, y: 0 };
115 | assert.deepEqual(service.position, expected, 'position is defaulted');
116 | service.update();
117 | assert.deepEqual(
118 | service.scrollMap,
119 | defaultScrollMap,
120 | 'does not set scrollMap b/c in fastboot'
121 | );
122 | });
123 |
124 | test('updating will set `scrollMap` if scrollElement is defined', function (assert) {
125 | const otherElem = document.createElement('div');
126 | otherElem.setAttribute('id', 'other-elem');
127 | const testing = document.querySelector('#ember-testing');
128 | testing.appendChild(otherElem);
129 | const service = this.owner
130 | .factoryFor('service:router-scroll')
131 | .create({ isFirstLoad: false, scrollElement: '#other-elem' });
132 | window.history.replaceState({ uuid: '123' }, null);
133 |
134 | let expected = { x: 0, y: 0 };
135 | assert.deepEqual(service.position, expected, 'position is defaulted');
136 | service.update();
137 | assert.deepEqual(
138 | service.scrollMap,
139 | {
140 | 123: expected,
141 | default: { x: 0, y: 0 },
142 | },
143 | 'sets scrollMap'
144 | );
145 | });
146 |
147 | test('updating will set default `scrollMap` if targetElement is defined', function (assert) {
148 | const otherElem = document.createElement('div');
149 | otherElem.setAttribute('id', 'other-elem');
150 | otherElem.style.position = 'relative';
151 | otherElem.style.top = '100px';
152 | const testing = document.querySelector('#ember-testing');
153 | testing.appendChild(otherElem);
154 | const service = this.owner
155 | .factoryFor('service:router-scroll')
156 | .create({ isFirstLoad: false, targetElement: '#other-elem' });
157 | window.history.replaceState({ uuid: '123' }, null);
158 |
159 | let expected = { x: 0, y: 0 };
160 | assert.deepEqual(service.position, expected, 'position is defaulted');
161 | service.update();
162 | assert.deepEqual(
163 | service.scrollMap,
164 | { 123: { x: 0, y: 100 }, default: { x: 0, y: 100 } },
165 | 'sets scrollMap'
166 | );
167 | });
168 |
169 | test('computing the position for an existing state uuid return the coords', function (assert) {
170 | const service = this.owner.lookup('service:router-scroll');
171 | window.history.replaceState({ uuid: '123' }, null);
172 |
173 | const expected = { x: 1, y: 1 };
174 | set(service, 'scrollMap.123', expected);
175 | assert.deepEqual(service.position, expected);
176 | });
177 |
178 | test('computing the position for a state without a cached scroll position returns default', function (assert) {
179 | const service = this.owner.lookup('service:router-scroll');
180 | const state = window.history.state;
181 | window.history.replaceState({ uuid: '123' }, null);
182 |
183 | const expected = { x: 0, y: 0 };
184 | assert.deepEqual(service.position, expected);
185 | window.history.replaceState(state, null);
186 | });
187 |
188 | test('computing the position for a non-existant state returns default', function (assert) {
189 | const service = this.owner.lookup('service:router-scroll');
190 |
191 | const expected = { x: 0, y: 0 };
192 | assert.deepEqual(service.position, expected);
193 | });
194 | });
195 |
--------------------------------------------------------------------------------
/addon/services/router-scroll.js:
--------------------------------------------------------------------------------
1 | import Service, { inject as service } from '@ember/service';
2 | import { set, get, action } from '@ember/object';
3 | import { typeOf } from '@ember/utils';
4 | import { assert } from '@ember/debug';
5 | import { getOwner } from '@ember/application';
6 | import { scheduleOnce } from '@ember/runloop';
7 | import { addListener, removeListener, sendEvent } from '@ember/object/events';
8 | import { setupRouter, whenRouteIdle } from 'ember-app-scheduler';
9 |
10 | let ATTEMPTS = 0;
11 | const MAX_ATTEMPTS = 100; // rAF runs every 16ms ideally, so 60x a second
12 |
13 | let requestId;
14 | let callbackRequestId;
15 |
16 | /**
17 | * By default, we start checking to see if the document height is >= the last known `y` position
18 | * we want to scroll to. This is important for content heavy pages that might try to scrollTo
19 | * before the content has painted
20 | *
21 | * @method tryScrollRecursively
22 | * @param {Function} fn
23 | * @param {Object} scrollHash
24 | * @param {Element} [element]
25 | * @param {string?} url
26 | * @void
27 | */
28 | function tryScrollRecursively(fn, scrollHash, element, url) {
29 | let documentHeight;
30 | // read DOM outside of rAF
31 | if (element) {
32 | documentHeight = Math.max(
33 | element.scrollHeight,
34 | element.offsetHeight,
35 | element.clientHeight
36 | );
37 | } else {
38 | const { body, documentElement: html } = document;
39 | documentHeight = Math.max(
40 | body.scrollHeight,
41 | body.offsetHeight,
42 | html.clientHeight,
43 | html.scrollHeight,
44 | html.offsetHeight
45 | );
46 | }
47 |
48 | callbackRequestId = window.requestAnimationFrame(() => {
49 | if (url && url.indexOf('#') > -1) {
50 | const hashElement = document.getElementById(url.split('#').pop());
51 | if (hashElement) {
52 | scrollHash = { x: hashElement.offsetLeft, y: hashElement.offsetTop };
53 | }
54 | }
55 | // write DOM (scrollTo causes reflow)
56 | if (documentHeight >= scrollHash.y || ATTEMPTS >= MAX_ATTEMPTS) {
57 | ATTEMPTS = 0;
58 | fn.call(null, scrollHash.x, scrollHash.y);
59 | } else {
60 | ATTEMPTS++;
61 | tryScrollRecursively(fn, scrollHash, element, url);
62 | }
63 | });
64 | }
65 |
66 | // to prevent scheduleOnce calling multiple times, give it the same ref to this function
67 | const CALLBACK = function (transition) {
68 | this.updateScrollPosition(transition);
69 | };
70 |
71 | class RouterScroll extends Service {
72 | @service router;
73 |
74 | get isFastBoot() {
75 | const fastboot = getOwner(this).lookup('service:fastboot');
76 | return fastboot ? fastboot.get('isFastBoot') : false;
77 | }
78 |
79 | key;
80 | targetElement;
81 | scrollElement = 'window';
82 | isFirstLoad = true;
83 | preserveScrollPosition = false;
84 | // ember-app-scheduler properties
85 | scrollWhenIdle = false;
86 | scrollWhenAfterRender = false;
87 |
88 | constructor() {
89 | super(...arguments);
90 |
91 | // https://github.com/ember-app-scheduler/ember-app-scheduler/pull/773
92 | setupRouter(this.router);
93 | }
94 |
95 | // eslint-disable-next-line ember/classic-decorator-hooks
96 | init(...args) {
97 | super.init(...args);
98 |
99 | this._loadConfig();
100 | set(this, 'scrollMap', {
101 | default: {
102 | x: 0,
103 | y: 0,
104 | },
105 | });
106 |
107 | addListener(this.router, 'routeWillChange', this._routeWillChange);
108 | addListener(this.router, 'routeDidChange', this._routeDidChange);
109 | }
110 |
111 | willDestroy() {
112 | removeListener(this.router, 'routeWillChange', this._routeWillChange);
113 | removeListener(this.router, 'routeDidChange', this._routeDidChange);
114 |
115 | if (requestId) {
116 | window.cancelAnimationFrame(requestId);
117 | }
118 |
119 | if (callbackRequestId) {
120 | window.cancelAnimationFrame(callbackRequestId);
121 | }
122 |
123 | super.willDestroy(...arguments);
124 | }
125 |
126 | /**
127 | * Updates the scroll position
128 | * it will be a single transition
129 | * @method updateScrollPosition
130 | * @param {transition|transition[]} transition If before Ember 3.6, this will be an array of transitions, otherwise
131 | */
132 | updateScrollPosition(transition) {
133 | if (this.isFirstLoad) {
134 | this.unsetFirstLoad();
135 | }
136 |
137 | let scrollPosition = this.position;
138 |
139 | // If `preserveScrollPosition` was not set on the controller, attempt fallback to `preserveScrollPosition` which was set on the router service.
140 | let preserveScrollPosition =
141 | (transition.router.currentRouteInfos || []).some(
142 | (routeInfo) => routeInfo.route.controller.preserveScrollPosition
143 | ) || this.preserveScrollPosition;
144 |
145 | if (!preserveScrollPosition) {
146 | const { scrollElement, targetElement, currentURL } = this;
147 |
148 | if (targetElement || 'window' === scrollElement) {
149 | tryScrollRecursively(window.scrollTo, scrollPosition, null, currentURL);
150 | } else if ('#' === scrollElement.charAt(0)) {
151 | const element = document.getElementById(scrollElement.substring(1));
152 |
153 | if (element) {
154 | let fn = (x, y) => {
155 | element.scrollLeft = x;
156 | element.scrollTop = y;
157 | };
158 | tryScrollRecursively(fn, scrollPosition, element, currentURL);
159 | }
160 | }
161 | }
162 |
163 | sendEvent(this, 'didScroll', transition);
164 | }
165 |
166 | @action
167 | _routeWillChange() {
168 | if (this.isFastBoot) {
169 | return;
170 | }
171 |
172 | this.update();
173 | }
174 |
175 | @action
176 | _routeDidChange(transition) {
177 | if (this.isFastBoot) {
178 | return;
179 | }
180 |
181 | const scrollWhenIdle = this.scrollWhenIdle;
182 | const scrollWhenAfterRender = this.scrollWhenAfterRender;
183 |
184 | if (!scrollWhenIdle && !scrollWhenAfterRender) {
185 | // out of the option, this happens on the tightest schedule
186 | scheduleOnce('render', this, CALLBACK, transition);
187 | } else if (scrollWhenAfterRender && !scrollWhenIdle) {
188 | // out of the option, this happens on the second tightest schedule
189 | scheduleOnce('afterRender', this, CALLBACK, transition);
190 | } else {
191 | whenRouteIdle().then(() => {
192 | this.updateScrollPosition(transition);
193 | });
194 | }
195 | }
196 |
197 | unsetFirstLoad() {
198 | set(this, 'isFirstLoad', false);
199 | }
200 |
201 | update() {
202 | if (this.isFastBoot || this.isFirstLoad) {
203 | return;
204 | }
205 |
206 | const scrollElement = this.scrollElement;
207 | const targetElement = this.targetElement;
208 | const scrollMap = this.scrollMap;
209 | const key = this.key;
210 | let x;
211 | let y;
212 |
213 | if (targetElement) {
214 | let element = document.querySelector(targetElement);
215 | if (element) {
216 | x = element.offsetLeft;
217 | y = element.offsetTop;
218 |
219 | // if we are looking to where to transition to next, we need to set the default to the position
220 | // of the targetElement on screen
221 | set(scrollMap, 'default', {
222 | x,
223 | y,
224 | });
225 | }
226 | } else if ('window' === scrollElement) {
227 | x = window.scrollX;
228 | y = window.scrollY;
229 | } else if ('#' === scrollElement.charAt(0)) {
230 | let element = document.getElementById(scrollElement.substring(1));
231 |
232 | if (element) {
233 | x = element.scrollLeft;
234 | y = element.scrollTop;
235 | }
236 | }
237 |
238 | // only a `key` present after first load
239 | if (key && 'number' === typeOf(x) && 'number' === typeOf(y)) {
240 | set(scrollMap, key, {
241 | x,
242 | y,
243 | });
244 | }
245 | }
246 |
247 | _loadConfig() {
248 | const config = getOwner(this).resolveRegistration('config:environment');
249 |
250 | if (config && config.routerScroll) {
251 | const scrollElement = config.routerScroll.scrollElement;
252 | const targetElement = config.routerScroll.targetElement;
253 |
254 | assert(
255 | 'You defined both scrollElement and targetElement in your config. We currently only support definining one of them',
256 | !(scrollElement && targetElement)
257 | );
258 |
259 | if ('string' === typeOf(scrollElement)) {
260 | set(this, 'scrollElement', scrollElement);
261 | }
262 |
263 | if ('string' === typeOf(targetElement)) {
264 | set(this, 'targetElement', targetElement);
265 | }
266 |
267 | const { scrollWhenIdle = false, scrollWhenAfterRender = false } =
268 | config.routerScroll;
269 | set(this, 'scrollWhenIdle', scrollWhenIdle);
270 | set(this, 'scrollWhenAfterRender', scrollWhenAfterRender);
271 | }
272 | }
273 | }
274 |
275 | Object.defineProperty(RouterScroll.prototype, 'position', {
276 | configurable: true,
277 | get() {
278 | const scrollMap = this.scrollMap;
279 | const stateUuid = window.history.state?.uuid;
280 |
281 | set(this, 'key', stateUuid);
282 | const key = this.key || '-1';
283 |
284 | return get(scrollMap, key) || scrollMap.default;
285 | },
286 | });
287 |
288 | export default RouterScroll;
289 |
--------------------------------------------------------------------------------
/tests/unit/router-scroll-test.js:
--------------------------------------------------------------------------------
1 | import EmberObject from '@ember/object';
2 | import { module, test } from 'qunit';
3 | import { setupTest } from 'ember-qunit';
4 | import { settled } from '@ember/test-helpers';
5 |
6 | let scrollTo, subject;
7 |
8 | module('router-scroll', function (hooks) {
9 | setupTest(hooks);
10 |
11 | hooks.beforeEach(function () {
12 | scrollTo = window.scrollTo;
13 | });
14 |
15 | hooks.afterEach(function () {
16 | window.scrollTo = scrollTo;
17 | });
18 |
19 | function getTransitionsMock(URL, isPreserveScroll) {
20 | Object.defineProperty(subject, 'currentURL', {
21 | value: URL || 'Hello/World',
22 | });
23 |
24 | const transition = {
25 | handler: {
26 | controller: {
27 | preserveScrollPosition: isPreserveScroll || false,
28 | },
29 | },
30 | router: {
31 | currentRouteInfos: [
32 | {
33 | route: {
34 | controller: {
35 | preserveScrollPosition: isPreserveScroll || false,
36 | },
37 | },
38 | },
39 | ],
40 | },
41 | };
42 |
43 | return transition;
44 | }
45 |
46 | test('when the application is FastBooted', async function (assert) {
47 | assert.expect(1);
48 | const done = assert.async();
49 |
50 | this.owner.register(
51 | 'service:fastboot',
52 | class extends EmberObject {
53 | isFastBoot = true;
54 | }
55 | );
56 | const routerScrollService = this.owner.lookup('service:router-scroll');
57 | routerScrollService.updateScrollPosition = () => {
58 | assert.notOk(true, 'it should not call updateScrollPosition.');
59 | done();
60 | };
61 |
62 | subject = this.owner.lookup('service:router');
63 | subject.trigger('routeDidChange');
64 |
65 | assert.ok(true, 'it should not call updateScrollPosition.');
66 | await settled();
67 | done();
68 | });
69 |
70 | test('when the application is not FastBooted', function (assert) {
71 | assert.expect(1);
72 | const done = assert.async();
73 |
74 | this.owner.register(
75 | 'service:fastboot',
76 | class extends EmberObject {
77 | isFastBoot = false;
78 | }
79 | );
80 | const routerScrollService = this.owner.lookup('service:router-scroll');
81 | routerScrollService.scrollWhenIdle = false;
82 | routerScrollService.updateScrollPosition = () => {
83 | assert.ok(true, 'it should call updateScrollPosition.');
84 | done();
85 | };
86 |
87 | subject = this.owner.lookup('service:router');
88 | subject.trigger('routeDidChange');
89 | });
90 |
91 | test('when the application is not FastBooted with targetElement', function (assert) {
92 | assert.expect(1);
93 | const done = assert.async();
94 |
95 | this.owner.register(
96 | 'service:fastboot',
97 | class extends EmberObject {
98 | isFastBoot = false;
99 | }
100 | );
101 | const routerScrollService = this.owner.lookup('service:router-scroll');
102 | routerScrollService.targetElement = '#myElement';
103 | routerScrollService.updateScrollPosition = () => {
104 | assert.ok(true, 'it should call updateScrollPosition.');
105 | done();
106 | };
107 |
108 | subject = this.owner.lookup('service:router');
109 | subject.trigger('routeDidChange');
110 | });
111 |
112 | test('when the application is not FastBooted with scrollWhenIdle', function (assert) {
113 | assert.expect(1);
114 | const done = assert.async();
115 |
116 | this.owner.register(
117 | 'service:fastboot',
118 | class extends EmberObject {
119 | isFastBoot = false;
120 | }
121 | );
122 | const routerScrollService = this.owner.lookup('service:router-scroll');
123 | routerScrollService.scrollWhenIdle = true;
124 | routerScrollService.updateScrollPosition = () => {
125 | assert.ok(true, 'it should call updateScrollPosition.');
126 | done();
127 | };
128 |
129 | subject = this.owner.lookup('service:router');
130 | subject.trigger('routeDidChange');
131 | });
132 |
133 | test('when the application is not FastBooted with scrollWhenAfterRender', function (assert) {
134 | assert.expect(1);
135 | const done = assert.async();
136 |
137 | this.owner.register(
138 | 'service:fastboot',
139 | class extends EmberObject {
140 | isFastBoot = false;
141 | }
142 | );
143 | const routerScrollService = this.owner.lookup('service:router-scroll');
144 | routerScrollService.scrollWhenAfterRender = true;
145 | routerScrollService.updateScrollPosition = () => {
146 | assert.ok(true, 'it should call updateScrollPosition.');
147 | done();
148 | };
149 |
150 | subject = this.owner.lookup('service:router');
151 | subject.trigger('routeDidChange');
152 | });
153 |
154 | test('Update Scroll Position: Position is preserved', async function (assert) {
155 | assert.expect(0);
156 |
157 | window.scrollTo = () => {
158 | assert.ok(false, 'Scroll To should not be called');
159 | };
160 |
161 | this.owner.register(
162 | 'service:fastboot',
163 | class extends EmberObject {
164 | isFastBoot = false;
165 | }
166 | );
167 | const routerScrollService = this.owner.lookup('service:router-scroll');
168 | Object.defineProperty(routerScrollService, 'position', {
169 | get position() {
170 | return { x: 0, y: 0 };
171 | },
172 | });
173 | routerScrollService.scrollElement = 'window';
174 |
175 | subject = this.owner.lookup('service:router');
176 | subject.trigger('routeDidChange', getTransitionsMock('Hello/World', true));
177 | await settled();
178 | });
179 |
180 | test('Update Scroll Position: Can preserve position using routerService', async function (assert) {
181 | assert.expect(0);
182 |
183 | window.scrollTo = () => {
184 | assert.ok(false, 'Scroll To should not be called');
185 | };
186 |
187 | const routerScrollService = this.owner.lookup('service:router-scroll');
188 | routerScrollService.preserveScrollPosition = true;
189 |
190 | subject = this.owner.lookup('service:router');
191 | subject.trigger('routeDidChange', getTransitionsMock('Hello/World', true));
192 | await settled();
193 | });
194 |
195 | test('Update Scroll Position: URL is an anchor', async function (assert) {
196 | assert.expect(2);
197 | const done = assert.async();
198 |
199 | const elem = document.createElement('div');
200 | elem.id = 'World';
201 | document.body.insertBefore(elem, null);
202 | window.scrollTo = (x, y) => {
203 | assert.strictEqual(
204 | x,
205 | elem.offsetLeft,
206 | `Scroll to called with correct horizontal offset x: ${x} === ${elem.offsetLeft}`
207 | );
208 | assert.strictEqual(
209 | y,
210 | elem.offsetTop,
211 | `Scroll to called with correct vertical offset y: ${y} === ${elem.offsetTop}`
212 | );
213 | done();
214 | };
215 |
216 | this.owner.register(
217 | 'service:fastboot',
218 | class extends EmberObject {
219 | isFastBoot = false;
220 | }
221 | );
222 | const routerScrollService = this.owner.lookup('service:router-scroll');
223 | routerScrollService.currentURL = '#World';
224 | Object.defineProperty(routerScrollService, 'position', {
225 | get() {
226 | return { x: 0, y: 0 };
227 | },
228 | });
229 | routerScrollService.scrollElement = 'window';
230 |
231 | subject = this.owner.lookup('service:router');
232 | subject.trigger(
233 | 'routeDidChange',
234 | getTransitionsMock('Hello/#World', false)
235 | );
236 | });
237 |
238 | test('Update Scroll Position: URL has nothing after an anchor', function (assert) {
239 | assert.expect(2);
240 | const done = assert.async();
241 |
242 | window.scrollTo = (x, y) => {
243 | assert.strictEqual(
244 | x,
245 | 1,
246 | 'Scroll to called with correct horizontal offset'
247 | );
248 | assert.strictEqual(y, 2, 'Scroll to called with correct vertical offset');
249 | done();
250 | };
251 |
252 | this.owner.register(
253 | 'service:fastboot',
254 | class extends EmberObject {
255 | isFastBoot = false;
256 | }
257 | );
258 | const routerScrollService = this.owner.lookup('service:router-scroll');
259 | Object.defineProperty(routerScrollService, 'position', {
260 | value: { x: 1, y: 2 },
261 | });
262 | routerScrollService.scrollElement = 'window';
263 |
264 | subject = this.owner.lookup('service:router');
265 | subject.trigger('routeDidChange', getTransitionsMock('Hello/#'));
266 | });
267 |
268 | test('Update Scroll Position: URL has nonexistent element after anchor', function (assert) {
269 | assert.expect(2);
270 | const done = assert.async();
271 |
272 | const elem = document.createElement('div');
273 | elem.id = 'World';
274 | document.body.insertBefore(elem, null);
275 | window.scrollTo = (x, y) => {
276 | assert.strictEqual(
277 | x,
278 | 1,
279 | 'Scroll to called with correct horizontal offset'
280 | );
281 | assert.strictEqual(y, 2, 'Scroll to called with correct vertical offset');
282 | done();
283 | };
284 |
285 | this.owner.register(
286 | 'service:fastboot',
287 | class extends EmberObject {
288 | isFastBoot = false;
289 | }
290 | );
291 | const routerScrollService = this.owner.lookup('service:router-scroll');
292 | Object.defineProperty(routerScrollService, 'position', {
293 | value: { x: 1, y: 2 },
294 | });
295 | routerScrollService.scrollElement = 'window';
296 |
297 | subject = this.owner.lookup('service:router');
298 | subject.trigger('routeDidChange', getTransitionsMock('Hello/#Bar'));
299 | });
300 |
301 | test('Update Scroll Position: Scroll Position is set by service', function (assert) {
302 | assert.expect(2);
303 | const done = assert.async();
304 |
305 | window.scrollTo = (x, y) => {
306 | assert.strictEqual(
307 | x,
308 | 1,
309 | 'Scroll to called with correct horizontal offset'
310 | );
311 | assert.strictEqual(
312 | y,
313 | 20,
314 | 'Scroll to called with correct vertical offset'
315 | );
316 | done();
317 | };
318 |
319 | this.owner.register(
320 | 'service:fastboot',
321 | class extends EmberObject {
322 | isFastBoot = false;
323 | }
324 | );
325 | const routerScrollService = this.owner.lookup('service:router-scroll');
326 | Object.defineProperty(routerScrollService, 'position', {
327 | value: { x: 1, y: 20 },
328 | });
329 | routerScrollService.scrollElement = 'window';
330 |
331 | subject = this.owner.lookup('service:router');
332 | subject.trigger('routeDidChange', getTransitionsMock('Hello/World'));
333 | });
334 | });
335 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ember-router-scroll
2 | ==============================================================================
3 |
4 | [](https://github.com/DockYard/ember-router-scroll/actions/workflows/ci.yml?query=branch%3Amaster)
5 |
6 | > Scroll to page top on transition, like a non-SPA website. An alternative scroll behavior for Ember applications.
7 |
8 | ## Why Use it?
9 |
10 | Ember expects an application to be rendered with nested views. The default behavior is for the scroll position to be
11 | preserved on every transition. However, not all Ember applications use nested views. For these applications, a user
12 | would expect to see the top of the page on most transitions.
13 |
14 | In addition to scrolling to the top of the page on most transitions, a user would expect the scroll position to be
15 | preserved when using the back or forward browser buttons.
16 |
17 | **ember-router-scroll** makes your single page application feel more like a regular website.
18 |
19 | Compatibility
20 | ------------------------------------------------------------------------------
21 |
22 | * Ember.js v3.12 or above
23 | * Ember CLI v3.12 or above
24 | * Node.js v12 or above
25 |
26 |
27 | Installation
28 | ------------------------------------------------------------------------------
29 |
30 | ```sh
31 | ember install ember-router-scroll
32 | ```
33 |
34 |
35 | Usage > 4.x
36 | ------------------------------------------------------------------------------
37 |
38 | Users do not need to import and extend from `ember-router-scroll` anymore. In order to upgrade, you should remove this import.
39 |
40 | This is what your `router.js` should look like.
41 |
42 | ```js
43 | import EmberRouter from '@ember/routing/router';
44 |
45 | export default class Router extends EmberRouter {
46 | ...
47 | }
48 | ```
49 |
50 | Usage < 4.x
51 | ------------------------------------------------------------------------------
52 |
53 | **1.** Import ember-router-scroll
54 |
55 | Add RouterScroll as an extension to your Router object. This class extends EmberRouter.
56 |
57 | ```javascript
58 | // app/router.js
59 |
60 | import EmberRouterScroll from 'ember-router-scroll';
61 |
62 | class Router extends EmberRouterScroll {
63 | ...
64 | }
65 | ```
66 |
67 | In version prior to v2.0, you can import the mixin and use it like so. This is necessary if your application does not support JavaScript classes yet.
68 |
69 | ```javascript
70 | // app/router.js
71 |
72 | import RouterScroll from 'ember-router-scroll';
73 |
74 | const Router = EmberRouter.extend(RouterScroll, {
75 | ...
76 | });
77 | ```
78 |
79 | Remaining optional steps for all versions 2.x - 4.x
80 | ------------------------------------------------------------------------------
81 |
82 | **2.** Enable `historySupportMiddleware` in your app
83 |
84 | Edit `config/environment.js` and add `historySupportMiddleware: true,` to get live-reload working in nested routes.
85 | (See [Issue #21](https://github.com/DockYard/ember-router-scroll/issues/21))
86 |
87 | ```javascript
88 | historySupportMiddleware: true,
89 | ```
90 |
91 | This location type inherits from Ember's `HistoryLocation`.
92 | ```
93 |
94 |
95 | ### Options
96 |
97 | #### Target Elements
98 |
99 | If you need to scroll to the top of an area that generates a vertical scroll bar, you can specify the id of an element
100 | of the scrollable area. Default is `window` for using the scroll position of the whole viewport. You can pass an options
101 | object in your application's `config/environment.js` file.
102 |
103 | ```javascript
104 | ENV['routerScroll'] = {
105 | scrollElement: '#mainScrollElement'
106 | };
107 | ```
108 |
109 | If you want to scroll to a target element on the page, you can specify the id or class of the element on the page. This
110 | is particularly useful if instead of scrolling to the top of the window, you want to scroll to the top of the main
111 | content area (that does not generate a vertical scrollbar).
112 |
113 | ```javascript
114 | ENV['routerScroll'] = {
115 | targetElement: '#main-target-element' // or .main-target-element
116 | };
117 | ```
118 |
119 | #### Scroll Timing
120 |
121 | You may want the default "out of the box" behaviour. We schedule scroll immediately after Ember's `render`. This occurs on the tightest schedule between route transition start and end.
122 |
123 | However, you have other options. If you need an extra tick after `render`, set `scrollWhenAfterRender: true`. You also may need to delay scroll functionality until the route is idle (approximately after the first paint completes) using `scrollWhenIdle: true` in your config. `scrollWhenIdle` && `scrollWhenAfterRender` defaults to `false`.
124 |
125 | This config property uses [`ember-app-scheduler`](https://github.com/ember-app-scheduler/ember-app-scheduler), so be sure to follow the instructions in the README. We include the `setupRouter` and `reset`. This all happens after `routeDidChange`.
126 |
127 | ```javascript
128 | ENV['routerScroll'] = {
129 | scrollWhenIdle: true // ember-app-scheduler
130 | };
131 | ```
132 |
133 | Or
134 |
135 | ```js
136 | ENV['routerScroll'] = {
137 | scrollWhenAfterRender: true // scheduleOnce('afterRender', ...)
138 | };
139 | ```
140 | I would suggest trying all of them out and seeing which works best for your app!
141 |
142 |
143 | ## A working example
144 |
145 | See [demo](https://dollarshaveclub.github.io/router-scroll-demo/) made by [Jon Chua](https://github.com/Chuabacca/).
146 |
147 |
148 | ## A visual demo
149 |
150 | ### Before
151 |
152 | 
153 |
154 | Notice that the in the full purple page, the user is sent to the **middle** of the page.
155 |
156 |
157 | ### After
158 |
159 | 
160 |
161 | Notice that the in the full purple page, the user is sent to the **top** of the page.
162 |
163 |
164 | ## Issues with nested routes
165 |
166 | ### Before:
167 |
168 | 
169 |
170 | Notice the unwanted scroll to top in this case.
171 |
172 |
173 | ### After:
174 |
175 | 
176 |
177 | Adding a query parameter or controller property fixes this issue.
178 |
179 |
180 | ### preserveScrollPosition with queryParams
181 |
182 | In certain cases, you might want to have certain routes preserve scroll position when coming from a specific location.
183 | For example, inside your application, there is a way to get to a route where the user expects scroll position to be
184 | preserved (such as a tab section).
185 |
186 | **1.** Add query param in controller
187 |
188 | Add `preserveScrollPosition` as a queryParam in the controller for the route that needs to preserve the scroll position.
189 |
190 | Example:
191 |
192 | ```javascript
193 | import Controller from '@ember/controller';
194 |
195 | export default class MyController extends Controller {
196 | queryParams = [
197 | 'preserveScrollPosition',
198 | ];
199 | }
200 | ```
201 |
202 | **2.** Pass in query param
203 |
204 | Next, in the place where a transition is triggered, pass in `preserveScrollPosition=true`. For example
205 |
206 | ```handlebars
207 |
208 | ```
209 |
210 |
211 | ### preserveScrollPosition with a controller property
212 |
213 | In other cases, you may have certain routes that always preserve scroll position, or routes where the controller can
214 | decide when to preserve scroll position. For instance, you may have some nested routes that have true nested UI where
215 | preserving scroll position is expected. Or you want a particular route to start off with the default scroll-to-top
216 | behavior but then preserve scroll position when query params change in response to user interaction. Using a controller
217 | property also allows the use of preserveScrollPosition without adding this to the query params.
218 |
219 | **1.** Add query param to controller
220 |
221 | Add `preserveScrollPosition` as a controller property for the route that needs to preserve the scroll position.
222 | In this example we have `preserveScrollPosition` initially set to false so that we get our normal scroll-to-top behavior
223 | when the route loads. Later on, when an action triggers a change to the `filter` query param, we also set
224 | `preserveScrollPosition` to true so that this user interaction does not trigger the scroll-to-top behavior.
225 |
226 | Example:
227 |
228 | ```javascript
229 | import Controller from '@ember/controller';
230 | import { action } from '@ember/object';
231 |
232 | export default class MyController extends Controller {
233 | queryParams = ['filter'];
234 |
235 | preserveScrollPosition = false;
236 |
237 | @action
238 | changeFilter(filter) {
239 | this.set('preserveScrollPosition', true);
240 | this.set('filter', filter);
241 | }
242 | }
243 | ```
244 |
245 | **2.** Reset preserveScrollPosition if necessary
246 |
247 | If your controller is changing the preserveScrollPosition property, you'll probably need to reset
248 | `preserveScrollPosition` back to the default behavior whenever the controller is reset. This is not necessary on routes
249 | where `preserveScrollPosition` is always set to true.
250 |
251 | ```javascript
252 | import Router from '@ember/routing/route';
253 |
254 | export default class MyRoute extends Route {
255 | resetController(controller) {
256 | controller.set('preserveScrollPosition', false);
257 | }
258 | }
259 | ```
260 |
261 |
262 | ### preserveScrollPosition via service
263 |
264 | You may need to programatically control `preserveScrollPosition` directly from a component. This can be achieved by toggling the `preserveScrollPosition` property on the `routerScroll` service.
265 |
266 | One common use case for this is when using query-param-based pagination on a page where `preserveScrollPosition` is expected to be false.
267 |
268 | For example, if a route should always scroll to top when loaded, `preserveScrollPosition` would be false. However, a user may then scroll down the page and paginate through some results (where each page is a query param). But because `preserveScrollPosition` is false, the page will scroll back to top on each of these paginations.
269 |
270 | This can be fixed by temporarily setting `preserveScrollPosition` to true on the service in the pagination transition action and then disabling `preserveScrollPosition` after the transition occurs.
271 |
272 | Note: if `preserveScrollPosition` is set to true on the service, it will override any values set on the current route's controller - whether query param or controller property.
273 |
274 |
275 | **1.** Manage preserveScrollPosition via service
276 |
277 | When you need to modify `preserveScrollPosition` on the service for a specific transition, you should always reset the value after the transition occurs, otherwise all future transitions will use the same `preserveScrollPosition` value.
278 |
279 | Example:
280 |
281 | ```javascript
282 | import Component from '@glimmer/component';
283 | import { inject as service } from '@ember/service';
284 | import { action } from '@ember/object';
285 |
286 | export default class MyComponent extends Component {
287 | @service routerScroll;
288 | @service router;
289 |
290 | @action
291 | async goToPaginationPage(pageNumber) {
292 | this.set('routerScroll.preserveScrollPosition', true);
293 | await this.router.transitionTo(
294 | this.router.currentRouteName,
295 | {
296 | queryParams: { page: pageNumber }
297 | }
298 | );
299 |
300 | // Reset `preserveScrollPosition` after transition so future transitions behave as expected
301 | this.set('routerScroll.preserveScrollPosition', false);
302 | }
303 | }
304 | ```
305 |
306 | ## Running Tests
307 |
308 | * `npm test` (Runs `ember try:testall` to test your addon against multiple Ember versions)
309 | * `ember test`
310 | * `ember test --serve
311 |
312 | License
313 | ------------------------------------------------------------------------------
314 |
315 | This project is licensed under the [MIT License](LICENSE.md).
316 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 4.1.2
4 | - Fix automatically scrolling to the element which id matches the hash segment of the url when there's loading substates.
5 |
6 | - Updating to the v3.0 series was due to removing `scrollWhenPainted` as a config option. Also, we fixed some hidden bugs with scheduling when to scroll to your last y position.
7 |
8 | ## v4.1.0 (2021-06-18)
9 |
10 | #### :rocket: Enhancement
11 | * [#283](https://github.com/DockYard/ember-router-scroll/pull/283) Upgrade ember-app-scheduler to the latest ([@SergeAstapov](https://github.com/SergeAstapov))
12 |
13 | #### Committers: 2
14 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
15 | - Sergey Astapov ([@SergeAstapov](https://github.com/SergeAstapov))
16 |
17 |
18 | ## 4.1.0 (2021-06-18)
19 |
20 | #### :rocket: Enhancement
21 | * [#283](https://github.com/DockYard/ember-router-scroll/pull/283) Upgrade ember-app-scheduler to the latest ([@SergeAstapov](https://github.com/SergeAstapov))
22 |
23 | #### Committers: 2
24 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
25 | - Sergey Astapov ([@SergeAstapov](https://github.com/SergeAstapov))
26 |
27 |
28 | ## v4.0.3 (2021-04-06)
29 |
30 | #### :bug: Bug Fix
31 | * [#280](https://github.com/DockYard/ember-router-scroll/pull/280) Run service teardown code in willDestroy() ([@SergeAstapov](https://github.com/SergeAstapov))
32 |
33 | #### Committers: 2
34 | - Dan Nelson ([@nadnoslen](https://github.com/nadnoslen))
35 | - Sergey Astapov ([@SergeAstapov](https://github.com/SergeAstapov))
36 |
37 |
38 | ## 4.0.3 (2021-04-06)
39 |
40 | #### :bug: Bug Fix
41 | * [#280](https://github.com/DockYard/ember-router-scroll/pull/280) Run service teardown code in willDestroy() ([@SergeAstapov](https://github.com/SergeAstapov))
42 |
43 | #### Committers: 2
44 | - Dan Nelson ([@nadnoslen](https://github.com/nadnoslen))
45 | - Sergey Astapov ([@SergeAstapov](https://github.com/SergeAstapov))
46 |
47 |
48 | ## v4.0.0 (2021-01-05)
49 |
50 | #### :rocket: Enhancement
51 | * [#270](https://github.com/DockYard/ember-router-scroll/pull/270) [Major]: refactor: use`RouterService` instead of Ember `Router` ([@snewcomer](https://github.com/snewcomer))
52 |
53 | #### Committers: 1
54 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
55 |
56 |
57 | ## 4.0.0 (2021-01-05)
58 |
59 | #### :rocket: Enhancement
60 | * [#270](https://github.com/DockYard/ember-router-scroll/pull/270) [Major]: refactor: use`RouterService` instead of Ember `Router` ([@snewcomer](https://github.com/snewcomer))
61 |
62 | #### Committers: 1
63 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
64 |
65 |
66 | ## v3.3.5 (2020-12-24)
67 |
68 | #### :rocket: Enhancement
69 | * [#269](https://github.com/DockYard/ember-router-scroll/pull/269) [Enhancement]: improve idle case with improved scrollTo under high loads ([@snewcomer](https://github.com/snewcomer))
70 |
71 | #### Committers: 1
72 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
73 |
74 |
75 | ## 3.3.5 (2020-12-24)
76 |
77 | #### :rocket: Enhancement
78 | * [#269](https://github.com/DockYard/ember-router-scroll/pull/269) [Enhancement]: improve idle case with improved scrollTo under high loads ([@snewcomer](https://github.com/snewcomer))
79 |
80 | #### Committers: 1
81 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
82 |
83 |
84 | ## v3.3.4 (2020-12-24)
85 |
86 | #### :rocket: Enhancement
87 | * [#266](https://github.com/DockYard/ember-router-scroll/pull/266) [Deps]: bump app-scheduler 5.1.1 ([@snewcomer](https://github.com/snewcomer))
88 |
89 | #### Committers: 2
90 | - Garrick Cheung ([@GCheung55](https://github.com/GCheung55))
91 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
92 |
93 |
94 | ## 3.3.4 (2020-12-24)
95 |
96 | #### :rocket: Enhancement
97 | * [#266](https://github.com/DockYard/ember-router-scroll/pull/266) [Deps]: bump app-scheduler 5.1.1 ([@snewcomer](https://github.com/snewcomer))
98 |
99 | #### Committers: 2
100 | - Garrick Cheung ([@GCheung55](https://github.com/GCheung55))
101 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
102 |
103 |
104 | ## v3.3.3 (2020-08-01)
105 |
106 | #### :rocket: Enhancement
107 | * [#257](https://github.com/DockYard/ember-router-scroll/pull/257) Run service teardown code in willDestroy() ([@SergeAstapov](https://github.com/SergeAstapov))
108 |
109 | #### Committers: 2
110 | - Bruno Casali ([@brunoocasali](https://github.com/brunoocasali))
111 | - Sergey Astapov ([@SergeAstapov](https://github.com/SergeAstapov))
112 |
113 |
114 | ## 3.3.3 (2020-08-01)
115 |
116 | #### :rocket: Enhancement
117 | * [#257](https://github.com/DockYard/ember-router-scroll/pull/257) Run service teardown code in willDestroy() ([@SergeAstapov](https://github.com/SergeAstapov))
118 |
119 | #### Committers: 2
120 | - Bruno Casali ([@brunoocasali](https://github.com/brunoocasali))
121 | - Sergey Astapov ([@SergeAstapov](https://github.com/SergeAstapov))
122 |
123 |
124 | ## v3.3.0 (2020-03-03)
125 |
126 | #### :rocket: Enhancement
127 | * [#246](https://github.com/DockYard/ember-router-scroll/pull/246) Add Evented trigger didScroll ([@snewcomer](https://github.com/snewcomer))
128 |
129 | #### Committers: 1
130 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
131 |
132 |
133 | ## 3.3.0 (2020-03-03)
134 |
135 | #### :rocket: Enhancement
136 | * [#246](https://github.com/DockYard/ember-router-scroll/pull/246) Add Evented trigger didScroll ([@snewcomer](https://github.com/snewcomer))
137 |
138 | #### Committers: 1
139 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
140 |
141 |
142 | ## v3.2.0 (2020-02-26)
143 |
144 | #### :rocket: Enhancement
145 | * [#244](https://github.com/DockYard/ember-router-scroll/pull/244) [ENHANCEMENT] Bring back scroll recursive ([@snewcomer](https://github.com/snewcomer))
146 |
147 | #### Committers: 1
148 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
149 |
150 |
151 | ## 3.2.0 (2020-02-26)
152 |
153 | #### :rocket: Enhancement
154 | * [#244](https://github.com/DockYard/ember-router-scroll/pull/244) [ENHANCEMENT] Bring back scroll recursive ([@snewcomer](https://github.com/snewcomer))
155 |
156 | #### Committers: 1
157 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
158 |
159 |
160 | ## v3.1.3 (2020-02-26)
161 |
162 | #### :bug: Bug Fix
163 | * [#242](https://github.com/DockYard/ember-router-scroll/pull/242) [BUG] update scroll position should only be called once ([@snewcomer](https://github.com/snewcomer))
164 |
165 | #### Committers: 1
166 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
167 |
168 |
169 | ## 3.1.3 (2020-02-26)
170 |
171 | #### :bug: Bug Fix
172 | * [#242](https://github.com/DockYard/ember-router-scroll/pull/242) [BUG] update scroll position should only be called once ([@snewcomer](https://github.com/snewcomer))
173 |
174 | #### Committers: 1
175 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
176 |
177 |
178 | ## v3.1.2 (2020-02-25)
179 |
180 | #### :bug: Bug Fix
181 | * [#241](https://github.com/DockYard/ember-router-scroll/pull/241) Prevent multiple calls on routeDidChange ([@snewcomer](https://github.com/snewcomer))
182 |
183 | #### Committers: 1
184 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
185 |
186 |
187 | ## 3.1.2 (2020-02-25)
188 |
189 | #### :bug: Bug Fix
190 | * [#241](https://github.com/DockYard/ember-router-scroll/pull/241) Prevent multiple calls on routeDidChange ([@snewcomer](https://github.com/snewcomer))
191 |
192 | #### Committers: 1
193 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
194 |
195 |
196 | ## 3.1.1 (2020-02-25)
197 |
198 | #### :bug: Bug Fix
199 | * [#240](https://github.com/DockYard/ember-router-scroll/pull/240) Missing setter for scrollWhenAfterRender ([@snewcomer](https://github.com/snewcomer))
200 |
201 | #### Committers: 1
202 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
203 |
204 |
205 | ## v3.1.0 (2020-02-25)
206 |
207 | #### :rocket: Enhancement
208 | * [#239](https://github.com/DockYard/ember-router-scroll/pull/239) Scroll when afterRender ([@snewcomer](https://github.com/snewcomer))
209 |
210 | #### Committers: 1
211 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
212 |
213 |
214 | ## 3.1.0 (2020-02-25)
215 |
216 | #### :rocket: Enhancement
217 | * [#239](https://github.com/DockYard/ember-router-scroll/pull/239) Scroll when afterRender ([@snewcomer](https://github.com/snewcomer))
218 |
219 | #### Committers: 1
220 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
221 |
222 |
223 | ## v3.0.0 (2020-02-24)
224 |
225 | #### :rocket: Enhancement
226 | * [#238](https://github.com/DockYard/ember-router-scroll/pull/238) Upgrade ember-app-scheduler to v4.0.0 ([@bobisjan](https://github.com/bobisjan))
227 |
228 | #### Committers: 1
229 | - Jan Bobisud ([@bobisjan](https://github.com/bobisjan))
230 |
231 |
232 | ## 3.0.0 (2020-02-24)
233 |
234 | #### :rocket: Enhancement
235 | * [#238](https://github.com/DockYard/ember-router-scroll/pull/238) Upgrade ember-app-scheduler to v4.0.0 ([@bobisjan](https://github.com/bobisjan))
236 |
237 | #### Committers: 1
238 | - Jan Bobisud ([@bobisjan](https://github.com/bobisjan))
239 |
240 |
241 | ## v2.0.1 (2020-02-21)
242 |
243 | #### :bug: Bug Fix
244 | * [#237](https://github.com/DockYard/ember-router-scroll/pull/237) Remove recursive scroll check ([@snewcomer](https://github.com/snewcomer))
245 |
246 | #### Committers: 2
247 | - Florian Pichler ([@pichfl](https://github.com/pichfl))
248 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
249 |
250 |
251 | ## 2.0.1 (2020-02-21)
252 |
253 | #### :bug: Bug Fix
254 | * [#237](https://github.com/DockYard/ember-router-scroll/pull/237) Remove recursive scroll check ([@snewcomer](https://github.com/snewcomer))
255 |
256 | #### Committers: 2
257 | - Florian Pichler ([@pichfl](https://github.com/pichfl))
258 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
259 |
260 |
261 | ## v2.0.0 (2020-01-17)
262 |
263 | #### :boom: Breaking Change
264 | * [#234](https://github.com/DockYard/ember-router-scroll/pull/234) [MAJOR] Class based elements ([@snewcomer](https://github.com/snewcomer))
265 |
266 | #### :rocket: Enhancement
267 | * [#234](https://github.com/DockYard/ember-router-scroll/pull/234) [MAJOR] Class based elements ([@snewcomer](https://github.com/snewcomer))
268 | * [#202](https://github.com/DockYard/ember-router-scroll/pull/202) Update to 3.12 ([@snewcomer](https://github.com/snewcomer))
269 |
270 | #### Committers: 1
271 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
272 |
273 |
274 | ## 2.0.0 (2020-01-17)
275 |
276 | #### :boom: Breaking Change
277 | * [#234](https://github.com/DockYard/ember-router-scroll/pull/234) [MAJOR] Class based elements ([@snewcomer](https://github.com/snewcomer))
278 |
279 | #### :rocket: Enhancement
280 | * [#234](https://github.com/DockYard/ember-router-scroll/pull/234) [MAJOR] Class based elements ([@snewcomer](https://github.com/snewcomer))
281 | * [#202](https://github.com/DockYard/ember-router-scroll/pull/202) Update to 3.12 ([@snewcomer](https://github.com/snewcomer))
282 |
283 | #### Committers: 1
284 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
285 |
286 |
287 | ## v1.3.3 (2019-10-13)
288 |
289 | #### :rocket: Enhancement
290 | * [#225](https://github.com/DockYard/ember-router-scroll/pull/225) Allow recording position with users first page visit ([@snewcomer](https://github.com/snewcomer))
291 |
292 | #### :bug: Bug Fix
293 | * [#225](https://github.com/DockYard/ember-router-scroll/pull/225) Allow recording position with users first page visit ([@snewcomer](https://github.com/snewcomer))
294 |
295 | #### Committers: 2
296 | - Mehul Kar ([@mehulkar](https://github.com/mehulkar))
297 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
298 |
299 |
300 | ## 1.3.3 (2019-10-13)
301 |
302 | #### :rocket: Enhancement
303 | * [#225](https://github.com/DockYard/ember-router-scroll/pull/225) Allow recording position with users first page visit ([@snewcomer](https://github.com/snewcomer))
304 |
305 | #### :bug: Bug Fix
306 | * [#225](https://github.com/DockYard/ember-router-scroll/pull/225) Allow recording position with users first page visit ([@snewcomer](https://github.com/snewcomer))
307 |
308 | #### Committers: 2
309 | - Mehul Kar ([@mehulkar](https://github.com/mehulkar))
310 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
311 |
312 |
313 | ## 1.3.1 (2019-09-12)
314 |
315 | #### :rocket: Enhancement
316 | * [#221](https://github.com/DockYard/ember-router-scroll/pull/221) Sn/1.3.1 ([@snewcomer](https://github.com/snewcomer))
317 | * [#219](https://github.com/DockYard/ember-router-scroll/pull/219) Dont batch reads and writes in same rAF ([@snewcomer](https://github.com/snewcomer))
318 |
319 | #### Committers: 1
320 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
321 |
322 |
323 | ## 1.3.0 (2019-09-11)
324 |
325 | #### :rocket: Enhancement
326 | * [#217](https://github.com/DockYard/ember-router-scroll/pull/217) Recursively check document height before scrollTo ([@snewcomer](https://github.com/snewcomer))
327 |
328 | #### Committers: 1
329 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
330 |
331 |
332 | ## 1.2.1 (2019-07-25)
333 |
334 | #### :rocket: Enhancement
335 | * [#216](https://github.com/DockYard/ember-router-scroll/pull/216) Update version to 1.2.1 ([@snewcomer](https://github.com/snewcomer))
336 | * [#213](https://github.com/DockYard/ember-router-scroll/pull/213) Allow configure so consuming app can override ([@snewcomer](https://github.com/snewcomer))
337 | * [#210](https://github.com/DockYard/ember-router-scroll/pull/210) Fix query-params-only transitions ([@CvX](https://github.com/CvX))
338 |
339 | #### :memo: Documentation
340 | * [#212](https://github.com/DockYard/ember-router-scroll/pull/212) Update Changelog 1.2.0 ([@snewcomer](https://github.com/snewcomer))
341 |
342 | #### Committers: 2
343 | - Jarek Radosz ([@CvX](https://github.com/CvX))
344 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
345 |
346 |
347 | ## 1.2.0 (2019-07-24)
348 |
349 | #### :boom: Breaking Change
350 | * [#203](https://github.com/DockYard/ember-router-scroll/pull/203) Deprecate locations/router-scroll for Embers own implementation ([@snewcomer](https://github.com/snewcomer))
351 |
352 | #### :rocket: Enhancement
353 | * [#204](https://github.com/DockYard/ember-router-scroll/pull/204) Release 1.2.0 ([@snewcomer](https://github.com/snewcomer))
354 | * [#203](https://github.com/DockYard/ember-router-scroll/pull/203) Deprecate locations/router-scroll for Embers own implementation ([@snewcomer](https://github.com/snewcomer))
355 |
356 | #### Committers: 4
357 | - Jarek Radosz ([@CvX](https://github.com/CvX))
358 | - Lou Greenwood ([@lougreenwood](https://github.com/lougreenwood))
359 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
360 | - Steve ([@st-h](https://github.com/st-h))
361 |
362 |
363 | ## 1.1.0 (2019-03-11)
364 |
365 | #### :rocket: Enhancement
366 | * [#187](https://github.com/DockYard/ember-router-scroll/pull/187) 1.1.0 minor version bump ([@snewcomer](https://github.com/snewcomer))
367 | * [#183](https://github.com/DockYard/ember-router-scroll/pull/183) Fix router event deprecations, update to Ember 3.7 ([@rwwagner90](https://github.com/rwwagner90))
368 |
369 | #### Committers: 2
370 | - Robert Wagner ([@rwwagner90](https://github.com/rwwagner90))
371 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
372 |
373 |
374 | ## 1.0.2 (2018-12-21)
375 |
376 | #### :rocket: Enhancement
377 | * [#178](https://github.com/DockYard/ember-router-scroll/pull/178) Update to 3.6 + bump ember-app-scheduler to fix deprecations ([@cibernox](https://github.com/cibernox))
378 |
379 | #### Committers: 2
380 | - Jeff Wainwright ([@yowainwright](https://github.com/yowainwright))
381 | - Miguel Camba ([@cibernox](https://github.com/cibernox))
382 |
383 |
384 | ## 1.0.1 (2018-11-05)
385 |
386 | #### :rocket: Enhancement
387 | * [#169](https://github.com/DockYard/ember-router-scroll/pull/169) Avoid scroll on first load ([@snewcomer](https://github.com/snewcomer))
388 |
389 | #### :bug: Bug Fix
390 | * [#169](https://github.com/DockYard/ember-router-scroll/pull/169) Avoid scroll on first load ([@snewcomer](https://github.com/snewcomer))
391 |
392 | #### Committers: 1
393 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
394 |
395 |
396 | ## 0.8.0 (2018-09-04)
397 |
398 | #### :rocket: Enhancement
399 | * [#150](https://github.com/DockYard/ember-router-scroll/pull/150) [init] adds initial work for ember-cli-update on greenkeeper PRs ([@yowainwright](https://github.com/yowainwright))
400 |
401 | #### Committers: 4
402 | - Greg Larrenaga ([@Duder-onomy](https://github.com/Duder-onomy))
403 | - Jeff Wainwright ([@yowainwright](https://github.com/yowainwright))
404 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
405 | - William Bautista ([@Willibaur](https://github.com/Willibaur))
406 |
407 |
408 | ## 0.6.1 (2018-05-08)
409 |
410 | #### :rocket: Enhancement
411 | * [#134](https://github.com/DockYard/ember-router-scroll/pull/134) [update] adds git auto-tag script, nvmrc + fixes ([@yowainwright](https://github.com/yowainwright))
412 | * [#101](https://github.com/DockYard/ember-router-scroll/pull/101) delayScrollTop config + schedule in render queue ([@snewcomer](https://github.com/snewcomer))
413 | * [#128](https://github.com/DockYard/ember-router-scroll/pull/128) [init] adds CODE_OF_CONDUCT.md ([@yowainwright](https://github.com/yowainwright))
414 | * [#114](https://github.com/DockYard/ember-router-scroll/pull/114) [update] removes .travis.yml ([@yowainwright](https://github.com/yowainwright))
415 | * [#113](https://github.com/DockYard/ember-router-scroll/pull/113) [feature] Feature add CircleCi ([@yowainwright](https://github.com/yowainwright))
416 | * [#109](https://github.com/DockYard/ember-router-scroll/pull/109) [init] adds initial automation of changelog.md, fixes changelog script ([@yowainwright](https://github.com/yowainwright))
417 |
418 | #### :bug: Bug Fix
419 | * [#120](https://github.com/DockYard/ember-router-scroll/pull/120) [BugFix] ensure correct intimate API is represented in transition mock ([@rondale-sc](https://github.com/rondale-sc))
420 |
421 | #### :memo: Documentation
422 | * [#101](https://github.com/DockYard/ember-router-scroll/pull/101) delayScrollTop config + schedule in render queue ([@snewcomer](https://github.com/snewcomer))
423 | * [#128](https://github.com/DockYard/ember-router-scroll/pull/128) [init] adds CODE_OF_CONDUCT.md ([@yowainwright](https://github.com/yowainwright))
424 |
425 | #### Committers: 5
426 | - Jeff Wainwright ([@yowainwright](https://github.com/yowainwright))
427 | - Jonathan ([@rondale-sc](https://github.com/rondale-sc))
428 | - Josh ([@joshkg](https://github.com/joshkg))
429 | - Robert Wagner ([@rwwagner90](https://github.com/rwwagner90))
430 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
431 |
432 |
433 | ## v0.6.0 (2018-04-02)
434 |
435 | #### :rocket: Enhancement
436 | * [#108](https://github.com/DockYard/ember-router-scroll/pull/108) [update] Adds DSC style linting ([@yowainwright](https://github.com/yowainwright))
437 |
438 | #### Committers: 7
439 | - Jeff Jewiss ([@jeffjewiss](https://github.com/jeffjewiss))
440 | - Jeff Wainwright ([@yowainwright](https://github.com/yowainwright))
441 | - Katherine ([@TerminalStar](https://github.com/TerminalStar))
442 | - Paul Nicholls ([@pauln](https://github.com/pauln))
443 | - Ryan Toronto ([@ryanto](https://github.com/ryanto))
444 | - Sam Selikoff ([@samselikoff](https://github.com/samselikoff))
445 | - Scott Newcomer ([@snewcomer](https://github.com/snewcomer))
446 |
447 |
448 | ## v0.5.0 (2018-02-01)
449 |
450 | #### :memo: Documentation
451 | * [#80](https://github.com/DockYard/ember-router-scroll/pull/80) Add CHANGELOG file ([@Turbo87](https://github.com/Turbo87))
452 |
453 | #### :house: Internal
454 | * [#78](https://github.com/DockYard/ember-router-scroll/pull/78) Allow tests to run by adding rootURL to asset paths. ([@ppegusii](https://github.com/ppegusii))
455 | * [#76](https://github.com/DockYard/ember-router-scroll/pull/76) Updates the demo app with the `rootUrl` ([@gowthamrm](https://github.com/gowthamrm))
456 | * [#75](https://github.com/DockYard/ember-router-scroll/pull/75) Add `ember-cli-github-pages` to deploy demo app ([@gowthamrm](https://github.com/gowthamrm))
457 | * [#74](https://github.com/DockYard/ember-router-scroll/pull/74) Moves the demo app inside the tests/dummy ([@gowthamrm](https://github.com/gowthamrm))
458 |
459 | #### Committers: 5
460 | - Brian Gonzalez ([@briangonzalez](https://github.com/briangonzalez))
461 | - Gowtham Raj ([@gowthamrm](https://github.com/gowthamrm))
462 | - Robert Webb ([@robert-j-webb](https://github.com/robert-j-webb))
463 | - Tobias Bieniek ([@Turbo87](https://github.com/Turbo87))
464 | - [@ppegusii](https://github.com/ppegusii)
465 |
466 |
467 | ## v0.4.0 (2017-10-08)
468 |
469 | #### :bug: Bug Fix
470 | * [#73](https://github.com/dollarshaveclub/ember-router-scroll/pull/73) Update ember-app-scheduler to 0.2.0 (Closes [#72](https://github.com/dollarshaveclub/ember-router-scroll/issues/72)). ([@YoranBrondsema](https://github.com/YoranBrondsema))
471 |
472 | #### Committers: 1
473 | - Yoran Brondsema ([YoranBrondsema](https://github.com/YoranBrondsema))
474 |
475 |
476 | ## v0.3.1 (2017-09-11)
477 |
478 | #### :rocket: Enhancement
479 | * [#67](https://github.com/dollarshaveclub/ember-router-scroll/pull/67) configurable scroll element. ([@ktrhn](https://github.com/ktrhn))
480 |
481 | #### :memo: Documentation
482 | * [#69](https://github.com/dollarshaveclub/ember-router-scroll/pull/69) Update readme with 'service:scheduler' dependency for unit tests. ([@barryofguilder](https://github.com/barryofguilder))
483 |
484 | #### :house: Internal
485 | * [#64](https://github.com/dollarshaveclub/ember-router-scroll/pull/64) Update "ember-cli-qunit" to v4.0.1. ([@Turbo87](https://github.com/Turbo87))
486 |
487 | #### Committers: 3
488 | - Jason Barry ([barryofguilder](https://github.com/barryofguilder))
489 | - Sebastian Helbig ([ktrhn](https://github.com/ktrhn))
490 | - Tobias Bieniek ([Turbo87](https://github.com/Turbo87))
491 |
492 |
493 | ## v0.3.0 (2017-08-02)
494 |
495 | #### :rocket: Enhancement
496 | * [#57](https://github.com/dollarshaveclub/ember-router-scroll/pull/57) Schedule window.scrollTo() to after content paint by using ember-app-scheduler. ([@cclo7](https://github.com/cclo7))
497 |
498 | #### :bug: Bug Fix
499 | * [#60](https://github.com/dollarshaveclub/ember-router-scroll/pull/60) made ember-app-scheduler a prod dep. ([@briangonzalez](https://github.com/briangonzalez))
500 |
501 | #### :memo: Documentation
502 | * [#51](https://github.com/dollarshaveclub/ember-router-scroll/pull/51) Updated docs. ([@briangonzalez](https://github.com/briangonzalez))
503 |
504 | #### :house: Internal
505 | * [#63](https://github.com/dollarshaveclub/ember-router-scroll/pull/63) testem: Use headless Chrome by default. ([@Turbo87](https://github.com/Turbo87))
506 | * [#62](https://github.com/dollarshaveclub/ember-router-scroll/pull/62) CI: Use yarn instead of npm. ([@Turbo87](https://github.com/Turbo87))
507 | * [#61](https://github.com/dollarshaveclub/ember-router-scroll/pull/61) Cleanup unused dependencies. ([@Turbo87](https://github.com/Turbo87))
508 | * [#58](https://github.com/dollarshaveclub/ember-router-scroll/pull/58) Update to Babel 6 and New Module Imports. ([@Turbo87](https://github.com/Turbo87))
509 |
510 | #### Committers: 3
511 | - Brian Gonzalez ([briangonzalez](https://github.com/briangonzalez))
512 | - Chiachi Lo ([cclo7](https://github.com/cclo7))
513 | - Tobias Bieniek ([Turbo87](https://github.com/Turbo87))
514 |
515 |
516 | ## v0.2.0 (2017-04-21)
517 |
518 | #### :memo: Documentation
519 | * [#44](https://github.com/dollarshaveclub/ember-router-scroll/pull/44) [DOC] Add comma in code snippet. ([@YoranBrondsema](https://github.com/YoranBrondsema))
520 |
521 | #### Committers: 2
522 | - Brian Gonzalez ([briangonzalez](https://github.com/briangonzalez))
523 | - Yoran Brondsema ([YoranBrondsema](https://github.com/YoranBrondsema))
524 |
525 |
526 | ## v0.1.1 (2017-02-01)
527 |
528 | #### :bug: Bug Fix
529 | * [#42](https://github.com/dollarshaveclub/ember-router-scroll/pull/42) Fix ember-getowner-polyfill deprecation. ([@krasnoukhov](https://github.com/krasnoukhov))
530 |
531 | #### Committers: 2
532 | - Brian Gonzalez ([briangonzalez](https://github.com/briangonzalez))
533 | - Dmitry Krasnoukhov ([krasnoukhov](https://github.com/krasnoukhov))
534 |
535 |
536 | ## v0.1.0 (2017-01-24)
537 |
538 | #### :rocket: Enhancement
539 | * [#40](https://github.com/dollarshaveclub/ember-router-scroll/pull/40) Align with Ember's HistoryLocation router. ([@briangonzalez](https://github.com/briangonzalez))
540 | * [#20](https://github.com/dollarshaveclub/ember-router-scroll/pull/20) Fastboot compatibility. ([@arjansingh](https://github.com/arjansingh))
541 |
542 | #### :memo: Documentation
543 | * [#41](https://github.com/dollarshaveclub/ember-router-scroll/pull/41) [README] A note about ember rfc and core implementation. ([@briangonzalez](https://github.com/briangonzalez))
544 | * [#36](https://github.com/dollarshaveclub/ember-router-scroll/pull/36) Remove unwanted whitespace. ([@andrewgordstewart](https://github.com/andrewgordstewart))
545 | * [#28](https://github.com/dollarshaveclub/ember-router-scroll/pull/28) Add field to config to get live-reload working. ([@bennycwong](https://github.com/bennycwong))
546 | * [#19](https://github.com/dollarshaveclub/ember-router-scroll/pull/19) Ember install.... ([@arjansingh](https://github.com/arjansingh))
547 | * [#18](https://github.com/dollarshaveclub/ember-router-scroll/pull/18) update readme. ([@bennycwong](https://github.com/bennycwong))
548 |
549 | #### :house: Internal
550 | * [#39](https://github.com/dollarshaveclub/ember-router-scroll/pull/39) travis fixes. ([@briangonzalez](https://github.com/briangonzalez))
551 |
552 | #### Committers: 4
553 | - Arjan Singh ([arjansingh](https://github.com/arjansingh))
554 | - Benny C. Wong ([bennycwong](https://github.com/bennycwong))
555 | - Brian Gonzalez ([briangonzalez](https://github.com/briangonzalez))
556 | - [andrewgordstewart](https://github.com/andrewgordstewart)
557 |
558 |
559 | ## v0.0.4 (2016-08-04)
560 |
561 | #### :bug: Bug Fix
562 | * [#14](https://github.com/dollarshaveclub/ember-router-scroll/pull/14) Enable backbutton for same routes. ([@bcardarella](https://github.com/bcardarella))
563 |
564 | #### Committers: 1
565 | - Brian Cardarella ([bcardarella](https://github.com/bcardarella))
566 |
567 |
568 | ## v0.0.3 (2016-08-04)
569 |
570 | #### :rocket: Enhancement
571 | * [#13](https://github.com/dollarshaveclub/ember-router-scroll/pull/13) move to index.js for easier importing. ([@bennycwong](https://github.com/bennycwong))
572 |
573 | #### :bug: Bug Fix
574 | * [#3](https://github.com/dollarshaveclub/ember-router-scroll/pull/3) Minor fixes. ([@bennycwong](https://github.com/bennycwong))
575 |
576 | #### :memo: Documentation
577 | * [#10](https://github.com/dollarshaveclub/ember-router-scroll/pull/10) use ember install instead of npm install. ([@bcardarella](https://github.com/bcardarella))
578 | * [#4](https://github.com/dollarshaveclub/ember-router-scroll/pull/4) Add readme. ([@jacefarm](https://github.com/jacefarm))
579 |
580 | #### Committers: 3
581 | - Benny C. Wong ([bennycwong](https://github.com/bennycwong))
582 | - Brian Cardarella ([bcardarella](https://github.com/bcardarella))
583 | - Jason Farmer ([jacefarm](https://github.com/jacefarm))
584 |
585 |
586 | ## v0.0.1 (2016-07-21)
587 |
588 | #### :rocket: Enhancement
589 | * [#1](https://github.com/dollarshaveclub/ember-router-scroll/pull/1) ERS creation. ([@jacefarm](https://github.com/jacefarm))
590 |
591 | #### :house: Internal
592 | * [#2](https://github.com/dollarshaveclub/ember-router-scroll/pull/2) increment version and add eslint. ([@bennycwong](https://github.com/bennycwong))
593 |
594 | #### Committers: 2
595 | - Benny C. Wong ([bennycwong](https://github.com/bennycwong))
596 | - Jason Farmer ([jacefarm](https://github.com/jacefarm))
597 |
--------------------------------------------------------------------------------