├── .editorconfig ├── .ember-cli ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .template-lintrc.js ├── .travis.yml ├── .watchmanconfig ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── addon ├── .gitkeep └── services │ └── scroller.js ├── app ├── .gitkeep └── services │ └── scroller.js ├── config ├── ember-try.js └── environment.js ├── ember-cli-build.js ├── index.js ├── package-lock.json ├── package.json ├── test └── application-test.js ├── testem.js ├── tests ├── dummy │ ├── app │ │ ├── app.js │ │ ├── components │ │ │ ├── .gitkeep │ │ │ └── element-scroll.js │ │ ├── controllers │ │ │ └── .gitkeep │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── instance-initializers │ │ │ └── scroller.js │ │ ├── models │ │ │ └── .gitkeep │ │ ├── resolver.js │ │ ├── router.js │ │ ├── routes │ │ │ └── .gitkeep │ │ ├── styles │ │ │ └── app.css │ │ └── templates │ │ │ ├── application.hbs │ │ │ └── components │ │ │ ├── .gitkeep │ │ │ └── element-scroll.hbs │ ├── config │ │ ├── environment.js │ │ ├── optional-features.json │ │ └── targets.js │ └── public │ │ └── robots.txt ├── helpers │ └── .gitkeep ├── index.html ├── integration │ └── .gitkeep ├── test-helper.js └── unit │ ├── .gitkeep │ └── services │ └── scroller-test.js └── vendor └── .gitkeep /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.hbs] 17 | insert_final_newline = false 18 | 19 | [*.{diff,md}] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | 17 | # ember-try 18 | /.node_modules.ember-try/ 19 | /bower.json.ember-try 20 | /package.json.ember-try 21 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | ecmaVersion: 2018, 5 | sourceType: 'module' 6 | }, 7 | plugins: [ 8 | 'ember' 9 | ], 10 | extends: [ 11 | 'eslint:recommended', 12 | 'plugin:ember/recommended', 13 | 'peopleconnect-ember' 14 | ], 15 | env: { 16 | browser: true 17 | }, 18 | rules: { 19 | }, 20 | overrides: [ 21 | // node files 22 | { 23 | files: [ 24 | '.eslintrc.js', 25 | '.template-lintrc.js', 26 | 'ember-cli-build.js', 27 | 'index.js', 28 | 'testem.js', 29 | 'blueprints/*/index.js', 30 | 'config/**/*.js', 31 | 'test/**/*.js', 32 | 'tests/dummy/config/**/*.js' 33 | ], 34 | excludedFiles: [ 35 | 'addon/**', 36 | 'addon-test-support/**', 37 | 'app/**', 38 | 'tests/dummy/app/**' 39 | ], 40 | parserOptions: { 41 | sourceType: 'script' 42 | }, 43 | env: { 44 | browser: false, 45 | node: true 46 | }, 47 | plugins: ['node'], 48 | rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, { 49 | // add your custom rules and overrides for node files here 50 | }) 51 | }, 52 | 53 | { 54 | files: ['test/**/*.js'], 55 | env: { 56 | mocha: true 57 | } 58 | }, 59 | 60 | { 61 | files: ['testem.js'], 62 | rules: { 63 | camelcase: 0 64 | } 65 | } 66 | ] 67 | }; 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | 7 | # dependencies 8 | /bower_components/ 9 | /node_modules/ 10 | 11 | # misc 12 | /.env* 13 | /.pnp* 14 | /.sass-cache 15 | /connect.lock 16 | /coverage/ 17 | /libpeerconnection.log 18 | /npm-debug.log* 19 | /testem.log 20 | /yarn-error.log 21 | 22 | # ember-try 23 | /.node_modules.ember-try/ 24 | /bower.json.ember-try 25 | /package.json.ember-try 26 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist/ 3 | /tmp/ 4 | 5 | # dependencies 6 | /bower_components/ 7 | 8 | # misc 9 | /.bowerrc 10 | /.editorconfig 11 | /.ember-cli 12 | /.env* 13 | /.eslintignore 14 | /.eslintrc.js 15 | /.gitignore 16 | /.template-lintrc.js 17 | /.travis.yml 18 | /.watchmanconfig 19 | /bower.json 20 | /config/ember-try.js 21 | /CONTRIBUTING.md 22 | /ember-cli-build.js 23 | /testem.js 24 | /tests/ 25 | /test/ 26 | /yarn.lock 27 | .gitkeep 28 | 29 | # ember-try 30 | /.node_modules.ember-try/ 31 | /bower.json.ember-try 32 | /package.json.ember-try 33 | -------------------------------------------------------------------------------- /.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended' 5 | }; 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | # we recommend testing addons with the same minimum supported node version as Ember CLI 5 | # so that your addon works for all apps 6 | - "8" 7 | 8 | sudo: false 9 | dist: trusty 10 | 11 | addons: 12 | chrome: stable 13 | 14 | cache: 15 | directories: 16 | - $HOME/.npm 17 | 18 | env: 19 | global: 20 | # See https://git.io/vdao3 for details. 21 | - JOBS=1 22 | 23 | branches: 24 | only: 25 | - master 26 | # npm version tags 27 | - /^v\d+\.\d+\.\d+/ 28 | 29 | jobs: 30 | fail_fast: true 31 | allow_failures: 32 | - env: EMBER_TRY_SCENARIO=ember-canary 33 | include: 34 | addons: skip 35 | env: mocha 36 | script: npm run mocha 37 | 38 | include: 39 | # runs linting and tests with current locked deps 40 | 41 | - stage: "Tests" 42 | name: "Tests" 43 | script: 44 | - npm run lint:hbs 45 | - npm run lint:js 46 | - npm test 47 | 48 | # we recommend new addons test the current and previous LTS 49 | # as well as latest stable release (bonus points to beta/canary) 50 | - stage: "Additional Tests" 51 | env: EMBER_TRY_SCENARIO=ember-lts-3.4 52 | - env: EMBER_TRY_SCENARIO=ember-lts-3.8 53 | - env: EMBER_TRY_SCENARIO=ember-release 54 | - env: EMBER_TRY_SCENARIO=ember-beta 55 | - env: EMBER_TRY_SCENARIO=ember-canary 56 | - env: EMBER_TRY_SCENARIO=ember-default-with-jquery 57 | 58 | script: 59 | - CONCURRENCY_SCROLL_TEST=true node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO 60 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How To Contribute 2 | 3 | ## Installation 4 | 5 | * `git clone ` 6 | * `cd my-addon` 7 | * `npm install` 8 | 9 | ## Linting 10 | 11 | * `npm run lint:hbs` 12 | * `npm run lint:js` 13 | * `npm run lint:js -- --fix` 14 | 15 | ## Running tests 16 | 17 | * `ember test` – Runs the test suite on the current Ember version 18 | * `ember test --server` – Runs the test suite in "watch mode" 19 | * `ember try:each` – Runs the test suite against multiple Ember versions 20 | 21 | ## Running the dummy application 22 | 23 | * `ember serve` 24 | * Visit the dummy application at [http://localhost:4200](http://localhost:4200). 25 | 26 | For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ember-concurrency-scroll 2 | ============================================================================== 3 | 4 | [![Build Status](https://travis-ci.org/peopleconnectus/ember-concurrency-scroll.svg?branch=master)](https://travis-ci.org/peopleconnectus/ember-concurrency-scroll) 5 | [![npm version](https://badge.fury.io/js/ember-concurrency-scroll.svg)](https://badge.fury.io/js/ember-concurrency-scroll) 6 | 7 | This addon provides a `scroller` service that leverages [ember-concurrency](http://ember-concurrency.com/) tasks to manage window and element scrolling. By using `ember-concurrency`, you can perform a scroll task, then chain a follow up task or action afterwards. This can be very useful in situations where you need to scroll to an element that needs the user's attention, by scrolling first, then calling the user's attention with some sort of behavior like a modal or popover. 8 | 9 | The other benefit to using `ember-concurrency` is that the scrolling task can be cancelled at any point, either by calling another scroll task, or explicitly cancelling it with cancelAll. 10 | 11 | 12 | Compatibility 13 | ------------------------------------------------------------------------------ 14 | 15 | * Ember.js v3.4 or above 16 | * Ember CLI v2.13 or above 17 | * Node.js v8 or above 18 | 19 | 20 | Installation 21 | ------------------------------------------------------------------------------ 22 | 23 | ``` 24 | ember install ember-concurrency-scroll 25 | ``` 26 | 27 | 28 | Usage 29 | ------------------------------------------------------------------------------ 30 | 31 | ## Features 32 | `ember-concurrency-scroll` offers three scrolling tasks via the `scroller service`, two of which have functions that return a task, with accompanying task versions: 33 | ### `scroller.scrollToElementId(id, options)` _(function)_ 34 | Primary use, allows you to scroll to a specific element by its `id` attribute. Returns an Ember Concurrency task. 35 | ### `scroller.scrollToElementIdTask(id, options)` _(async function)_ 36 | Ember Concurrency task that version of `scrollToElementId`. Primary use, allows you to scroll to a specific element by its `id` attribute. 37 | 38 | ### `scroller.scrollToElement(element, options)` _(function)_ 39 | Allows you to scroll to an element by passing the element itself. Useful for components to scroll to themselves if they're out of the viewport. Returns an Ember Concurrency task. 40 | 41 | ### `scroller.scrollToElementTask(element, options)` _(async function)_ 42 | Ember Concurrency task version of `scrollToElement`. Allows you to scroll to an element by passing the element itself. Useful for components to scroll to themselves if they're out of the viewport. 43 | 44 | ### `scroller.scrollTo(start, end, options)` _(function)_ 45 | Core function, handles actual scrolling via easing, calling `window.scrollTo` or setting the value via `element.scrollTop` and `element.scrollLeft`. Start and end values can be either numeric (when we only want to scroll in one axis), OR they can be coordinate objects containing an x and y value ({x:0, y:0}). Returns an Ember Concurrency task. 46 | 47 | ### `scroller.scrollToTask(start, end, options)` _(async function)_ 48 | Core task, handles actual scrolling via easing, calling `window.scrollTo` or setting the value via `element.scrollTop` and `element.scrollLeft`. Start and end values can be either numeric (when we only want to scroll in one axis), OR they can be coordinate objects containing an x and y value ({x:0, y:0}). 49 | 50 | #### Options 51 | ##### duration _integer_ 52 | Scroll duration in milliseconds. Default is `1000`. 53 | 54 | ##### padding _number_/_object_ 55 | Adds an offset to the number of pixels above and to the left of the target element when scrolling. Can either be a number (used for both `x` and `y` values), or an object containing `x` and `y` values. Useful if you want to scroll using `scrollToElement`, but don't want to scroll to the exact position of the element. Can also be a negative value, to scroll past the target element position. Default is `20`. 56 | 57 | ##### easeType _string_ 58 | Easing type used in scroll. Default is `sinusoidal`. 59 | 60 | Easing options are available (via the [`node-easing` library](https://github.com/rook2pawn/node-easing)), but the default is a sinusoidal ease. The available easing types are: 61 | 62 | * `cubic` 63 | * `circular` 64 | * `exponential` 65 | * `linear` 66 | * `quartic` 67 | * `quadratic` 68 | * `quintic` 69 | * `sinusoidal` (default) 70 | 71 | The number of ease steps are calculated using the scroll duration value (default is 1000ms), which results in an array of predefined easing steps used for the scrolling. 72 | 73 | ##### axis _string_ 74 | Determines the axis to scroll on, when the `start` and `end` values are numeric. Default is 'y'. 75 | 76 | ##### ignoreViewport _boolean_ 77 | Determines if the scrollTo task should scroll if the target position is already in the viewport. Default is `true`. 78 | 79 | ##### container _string_/_element_ 80 | A DOM element or id to target for scrolling. Allows you to scroll the contents of fixed size elements with overflow property set to scroll. See the example below for further explanation. 81 | 82 | ### scroller.cancelAll() 83 | Cancels all scrolling tasks. Useful to interrupt scrolling if the user scrolls during a scrolling task. 84 | 85 | ## Usage 86 | 87 | ```js 88 | // example element-scroll component 89 | import Component from '@ember/component'; 90 | import { inject as service } from '@ember/service'; 91 | import { task } from 'ember-concurrency'; 92 | export default Component.extend({ 93 | tagName: 'button', 94 | scroller: service(), 95 | scroll: task(function *() { 96 | // It is recommended to wrap the scroller service task in component task to allow for cleanup if the component is destroyed mid task 97 | yield this.get('scroller').scrollToElementId(...arguments); 98 | }), 99 | click() { 100 | this.get('scroll').perform(this.get('target'), { 101 | duration: 1500, 102 | easeType: 'linear', 103 | // padding determines the how far above or to the left of the target element to scroll to, so we don't scroll to the exact edge of the element 104 | padding: { 105 | x: 100, 106 | y: 100 107 | } 108 | }); 109 | } 110 | }); 111 | 112 | // implementing the component 113 | {{#element-scroll target="title"}}scroll to title{{/element-scroll}} 114 | ``` 115 | Here are some ways for calling the scrollTo task directly. Do note that it is recommended that you follow `ember-concurrency`'s guidelines in implementing these tasks in your code, as calling them anywhere without managing the concurrency may have unintended results (like double scrolling or scrolls cancelling immediately): 116 | ```js 117 | // scroll on the y axis 118 | this.get('scroller').scrollTo(0, 1000); 119 | // scroll on the x axis 120 | this.get('scroller').scrollTo(0, 1000, { axis: 'x' }); 121 | // scroll on both x and y 122 | this.get('scroller').scrollTo({ x: 0, y: 0}, { x: 1000, y: 1000}); 123 | ``` 124 | 125 | You can limit the scroll to a specific element, for instance, if you had a fixed size container that you wanted to scroll to a specific element inside that container, you'd pass either the container id or the element as the `container` option. Note that if you do use the container option, it will only scroll inside the container, and the window will not scroll to the container element itself. You could solve this by first scrolling to the container, then scrolling to the element inside the container with the container option. 126 | ```hbs 127 |
128 |
129 | ... stuff 130 |
131 |
132 | ... titles 133 |
134 |
135 | ``` 136 | ```js 137 | scrollToContentsTitle : task(function *() { 138 | yield this.get('scroller').scrollToElementId('contents'); 139 | yield this.get('scroller').scrollToElementId('title', {container: 'contents'}); 140 | }) 141 | ``` 142 | 143 | You can also use the `perform` helper to call the tasks from inside a template, but it's not the recommended implementation so use at your own risk. 144 | ```hbs 145 | // note that options can be passed using the hash helper 146 | 147 | ``` 148 | 149 | ## Config 150 | The scroller defaults can be set in `config/environment.js`, allowing you to set the defaults for your entire app, rather than having to override every time you use the scroller. 151 | 152 | ```js 153 | // config/environment.js 154 | /* eslint-env node */ 155 | 'use strict'; 156 | 157 | module.exports = function(environment) { 158 | let ENV = { 159 | modulePrefix: 'dummy', 160 | ... 161 | // add your defaults at the root level of the config 162 | 'ember-concurrency-scroll': { 163 | duration: 1400, 164 | easeType: 'linear', 165 | padding: { 166 | x: 0, 167 | y: 0 168 | }, 169 | 170 | // you can also set overrides which take preference over everything 171 | // this is useful for testing purposes 172 | // default is duration: 1 in testing mode 173 | overrides: { 174 | } 175 | } 176 | } 177 | ``` 178 | 179 | 180 | Contributing 181 | ------------------------------------------------------------------------------ 182 | 183 | See the [Contributing](CONTRIBUTING.md) guide for details. 184 | 185 | 186 | License 187 | ------------------------------------------------------------------------------ 188 | 189 | This project is licensed under the [MIT License](LICENSE.md). 190 | -------------------------------------------------------------------------------- /addon/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peopleconnectus/ember-concurrency-scroll/a917a525619d3ca4cb8c0da1cfcf4834aa687799/addon/.gitkeep -------------------------------------------------------------------------------- /addon/services/scroller.js: -------------------------------------------------------------------------------- 1 | import Service from '@ember/service'; 2 | import { assert } from '@ember/debug'; 3 | import Easing from 'easing/browser-easing'; 4 | import { task, timeout } from 'ember-concurrency'; 5 | import config from 'ember-get-config'; 6 | import window from 'ember-window-mock'; 7 | import { assign } from '@ember/polyfills'; 8 | 9 | const easeTypes = ['linear', 'quadratic', 'cubic', 'quartic', 'quintic', 'sinusoidal', 'sin', 'circular', 'exponential']; 10 | 11 | export default Service.extend({ 12 | init() { 13 | this._super(...arguments); 14 | let conf = config['ember-concurrency-scroll']; 15 | let padding = typeof conf.padding === 'undefined' ? 20 : conf.padding; 16 | let ignoreViewport = typeof conf.ignoreViewport === 'undefined' ? true : conf.ignoreViewport; 17 | let defaults = { 18 | axis: conf.axis || 'y', 19 | duration: conf.duration || 1000, 20 | easeType: conf.easeType || 'sinusoidal', 21 | ignoreViewport, 22 | padding: { 23 | x: padding && padding.x || padding, 24 | y: padding && padding.y || padding 25 | } 26 | }; 27 | this.set('defaults', defaults); 28 | this.set('overrides', conf.overrides); 29 | }, 30 | 31 | // scroll to an element by id 32 | scrollToElementId() { 33 | return this.get('scrollToElementIdTask').perform(...arguments); 34 | }, 35 | 36 | // task version so you can use the perform helper 37 | scrollToElementIdTask: task(function * (elementId, options = {}) { 38 | let element = document && document.getElementById(elementId); 39 | assert(`An element with the id: '${elementId}' could not be found in the DOM. Be sure to check that it has been rendered before attempting to scroll to it.`, element); 40 | yield this.get('scrollToElementTask').perform(element, options); 41 | }), 42 | 43 | // scroll to an element 44 | scrollToElement() { 45 | return this.get('scrollToElementTask').perform(...arguments); 46 | }, 47 | 48 | // task version so you can use the perform helper 49 | scrollToElementTask: task(function * (element, options = {}) { 50 | let start = { 51 | y: this.getDocumentScrollTop(), 52 | x: this.getDocumentScrollLeft() 53 | }; 54 | let end; 55 | // if we're targeting a container, account for offset to start and end 56 | if (options.container) { 57 | end = { 58 | y: element.offsetTop, 59 | x: element.offsetLeft 60 | }; 61 | let container = this.getContainer(options.container); 62 | start.y = container.scrollTop; 63 | end.y = end.y - container.offsetTop; 64 | start.x = container.scrollLeft; 65 | end.x = end.x - container.offsetLeft; 66 | } else { 67 | end = { 68 | y: window.pageYOffset + element.getBoundingClientRect().top, 69 | x: window.pageXOffset + element.getBoundingClientRect().left 70 | }; 71 | } 72 | yield this.get('scrollToTask').perform(start, end, options); 73 | }), 74 | 75 | // scroll to a position 76 | scrollTo() { 77 | return this.get('scrollToTask').perform(...arguments); 78 | }, 79 | 80 | // start position, end position, duration in ms, easetype 81 | scrollToTask: task(function * (start, end, options = {}) { 82 | options = assign({}, this.get('defaults'), options, this.get('overrides')); 83 | let axis = options.axis; 84 | let ignoreViewport = options.ignoreViewport; 85 | let container = options.container && this.getContainer(options.container) || window; 86 | let easeType = options.easeType; 87 | let duration = options.duration; 88 | let scrollTo = this.getScrollTo(container); 89 | let viewportHeight = container.innerHeight || container.clientHeight || document.documentElement.clientHeight; 90 | let viewportWidth = container.innerWidth || container.clientWidth || document.documentElement.clientWidth; 91 | let startX; 92 | let startY; 93 | let endX; 94 | let endY; 95 | let paddingX; 96 | let paddingY; 97 | if (options.padding) { 98 | assert('The padding option must have x and y properties', typeof options.padding.x !== 'undefined' && typeof options.padding.y !== 'undefined'); 99 | paddingX = options.padding.x; 100 | paddingY = options.padding.y; 101 | } 102 | if (typeof start === 'object') { 103 | assert('The start argument must have x and y properties', typeof start.x !== 'undefined' && typeof start.y !== 'undefined'); 104 | assert('The end argument must have x and y properties', typeof end.x !== 'undefined' && typeof end.y !== 'undefined'); 105 | startX = start.x; 106 | startY = start.y; 107 | endX = end.x; 108 | endY = end.y; 109 | // if the end is within the viewport, we don't need to scroll that axis 110 | if (!ignoreViewport && start.x <= end.x && end.x <= viewportWidth) { 111 | axis = 'y'; 112 | } else if (!ignoreViewport && start.y <= end.y && end.y <= viewportHeight) { 113 | axis = 'x'; 114 | } else { 115 | axis = 'xy'; 116 | } 117 | } else if (axis === 'y') { 118 | startY = start; 119 | endY = end; 120 | startX = endX = container.scrollLeft || container.scrollX; 121 | } else { 122 | startX = start; 123 | endX = end; 124 | startY = endY = container.scrollTop || container.scrollY; 125 | } 126 | assert(`"${options.type}" is not a valid easeType. It must be one of these options: ${easeTypes}`, easeTypes.indexOf(easeType) !== -1); 127 | // x and y easing variables 128 | let index = 0; 129 | let delay = duration * 0.001; 130 | let steps = Math.ceil(duration * 0.1); 131 | let targetY = endY - startY - paddingY; 132 | let targetX = endX - startX - paddingX; 133 | let offsetY = startY; 134 | let offsetX = startX; 135 | let dirY = 1; 136 | let dirX = 1; 137 | let eases = Easing(steps, easeType); 138 | 139 | if (startY > endY) { 140 | targetY = startY - endY + paddingY; 141 | dirY = -1; 142 | } 143 | 144 | if (startX > endX) { 145 | targetX = startX - endX + paddingX; 146 | dirX = -1; 147 | } 148 | // Ember.Logger.log(start, end, `tx:${targetX}, ty:${targetY}, ox:${offsetX}, oy:${offsetY}, ${axis}`) 149 | while (index < steps) { 150 | if (axis === 'x') { 151 | // scroll x axis 152 | scrollTo(eases[index] * targetX * dirX + offsetX, startY); 153 | } else if (axis === 'xy' || axis === 'both') { 154 | // scroll x and y axis 155 | scrollTo(eases[index] * targetX * dirX + offsetX, eases[index] * targetY * dirY + offsetY); 156 | } else { 157 | // scroll y axis 158 | scrollTo(startX, eases[index] * targetY * dirY + offsetY); 159 | } 160 | index++; 161 | yield timeout(delay); 162 | } 163 | }).keepLatest(), 164 | 165 | cancelAll() { 166 | this.get('scrollTo').cancelAll(); 167 | }, 168 | 169 | getScrollTo(container) { 170 | // if scrollTo is a function, it's most likely the window 171 | if (typeof container.scrollTo === 'function') { 172 | return container.scrollTo; 173 | // otherwise it's an element 174 | } else { 175 | return (x, y) => { 176 | container.scrollLeft = x; 177 | container.scrollTop = y; 178 | }; 179 | } 180 | }, 181 | 182 | getContainer(container) { 183 | if (typeof container !== 'string') { 184 | return container; 185 | } else { 186 | // assume the string is an element id 187 | return document && document.getElementById(container); 188 | } 189 | }, 190 | 191 | getDocumentScrollTop() { 192 | assert('document is not available', document); 193 | return document && document.documentElement.scrollTop || document && document.body.scrollTop || 0; 194 | }, 195 | 196 | getDocumentScrollLeft() { 197 | assert('document is not available', document); 198 | return document && document.documentElement.scrollLeft || document && document.body.scrollLeft || 0; 199 | } 200 | }); 201 | -------------------------------------------------------------------------------- /app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peopleconnectus/ember-concurrency-scroll/a917a525619d3ca4cb8c0da1cfcf4834aa687799/app/.gitkeep -------------------------------------------------------------------------------- /app/services/scroller.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-concurrency-scroll/services/scroller'; 2 | -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getChannelURL = require('ember-source-channel-url'); 4 | 5 | module.exports = async function() { 6 | return { 7 | scenarios: [ 8 | { 9 | name: 'ember-lts-3.4', 10 | npm: { 11 | devDependencies: { 12 | 'ember-source': '~3.4.0' 13 | } 14 | } 15 | }, 16 | { 17 | name: 'ember-lts-3.8', 18 | npm: { 19 | devDependencies: { 20 | 'ember-source': '~3.8.0' 21 | } 22 | } 23 | }, 24 | { 25 | name: 'ember-release', 26 | npm: { 27 | devDependencies: { 28 | 'ember-source': await getChannelURL('release') 29 | } 30 | } 31 | }, 32 | { 33 | name: 'ember-beta', 34 | npm: { 35 | devDependencies: { 36 | 'ember-source': await getChannelURL('beta') 37 | } 38 | } 39 | }, 40 | { 41 | name: 'ember-canary', 42 | npm: { 43 | devDependencies: { 44 | 'ember-source': await getChannelURL('canary') 45 | } 46 | } 47 | }, 48 | // The default `.travis.yml` runs this scenario via `npm test`, 49 | // not via `ember try`. It's still included here so that running 50 | // `ember try:each` manually or from a customized CI config will run it 51 | // along with all the other scenarios. 52 | { 53 | name: 'ember-default', 54 | npm: { 55 | devDependencies: {} 56 | } 57 | }, 58 | { 59 | name: 'ember-default-with-jquery', 60 | env: { 61 | EMBER_OPTIONAL_FEATURES: JSON.stringify({ 62 | 'jquery-integration': true 63 | }) 64 | }, 65 | npm: { 66 | devDependencies: { 67 | '@ember/jquery': '^0.5.1' 68 | } 69 | } 70 | } 71 | ] 72 | }; 73 | }; 74 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(environment, appConfig) { 4 | let testingOverrides = {}; 5 | if (environment === 'test' && !process.env.CONCURRENCY_SCROLL_TEST) { 6 | testingOverrides.duration = 1; 7 | } 8 | return { 9 | 'ember-concurrency-scroll': { 10 | overrides: Object.assign( 11 | {}, 12 | testingOverrides, 13 | (appConfig['ember-concurrency-scroll'] || {}).overrides || {} 14 | ) 15 | } 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 4 | 5 | module.exports = function(defaults) { 6 | let app = new EmberAddon(defaults, { 7 | // Add options here 8 | }); 9 | 10 | /* 11 | This build file specifies the options for the dummy test app of this 12 | addon, located in `/tests/dummy` 13 | This build file does *not* influence how the addon or the app using it 14 | behave. You most likely want to be modifying `./index.js` or app's build file 15 | */ 16 | 17 | return app.toTree(); 18 | }; 19 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | name: require('./package').name 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-concurrency-scroll", 3 | "version": "1.0.0", 4 | "description": "A service to manage window and element scrolling using ember-concurrency.", 5 | "keywords": [ 6 | "easing", 7 | "ember-addon" 8 | ], 9 | "repository": "https://github.com/peopleconnectus/ember-concurrency-scroll.git", 10 | "license": "MIT", 11 | "author": "Jake Bixby", 12 | "directories": { 13 | "doc": "doc", 14 | "test": "tests" 15 | }, 16 | "scripts": { 17 | "build": "ember build", 18 | "lint:hbs": "ember-template-lint .", 19 | "lint:js": "eslint .", 20 | "start": "ember serve", 21 | "test": "cross-env CONCURRENCY_SCROLL_TEST=true ember test", 22 | "test:all": "ember try:each", 23 | "mocha": "cross-env DEBUG=ember-cli-addon-tests mocha" 24 | }, 25 | "dependencies": { 26 | "easing": "1.2.1", 27 | "ember-auto-import": "^1.5.2", 28 | "ember-cli-babel": "^7.8.0", 29 | "ember-concurrency": "^1.0.0", 30 | "ember-get-config": "^0.2.4", 31 | "ember-window-mock": "^0.5.4" 32 | }, 33 | "devDependencies": { 34 | "@ember/optional-features": "^0.7.0", 35 | "broccoli-asset-rev": "^3.0.0", 36 | "chai": "^4.2.0", 37 | "cross-env": "^5.2.0", 38 | "denodeify": "^1.2.1", 39 | "ember-cli": "~3.11.0", 40 | "ember-cli-addon-tests": "^0.11.1", 41 | "ember-cli-dependency-checker": "^3.2.0", 42 | "ember-cli-eslint": "^5.1.0", 43 | "ember-cli-htmlbars": "^3.1.0", 44 | "ember-cli-htmlbars-inline-precompile": "^2.1.0", 45 | "ember-cli-inject-live-reload": "^2.0.1", 46 | "ember-cli-sri": "^2.1.1", 47 | "ember-cli-template-lint": "^1.0.0-beta.1", 48 | "ember-cli-uglify": "^3.0.0", 49 | "ember-disable-prototype-extensions": "^1.1.3", 50 | "ember-export-application-global": "^2.0.0", 51 | "ember-load-initializers": "^2.0.0", 52 | "ember-maybe-import-regenerator": "^0.1.6", 53 | "ember-qunit": "^4.4.1", 54 | "ember-resolver": "^5.1.3", 55 | "ember-sinon": "^4.0.0", 56 | "ember-source": "~3.11.1", 57 | "ember-source-channel-url": "^2.0.1", 58 | "ember-try": "^1.2.1", 59 | "eslint": "^6.0.1", 60 | "eslint-config-peopleconnect": "^2.0.1", 61 | "eslint-config-peopleconnect-ember": "^5.0.0", 62 | "eslint-config-sane": "^0.8.2", 63 | "eslint-plugin-ember": "^6.7.1", 64 | "eslint-plugin-hbs": "^0.1.1", 65 | "eslint-plugin-json-files": "^0.6.1", 66 | "eslint-plugin-node": "^9.1.0", 67 | "eslint-plugin-prefer-let": "^1.0.1", 68 | "loader.js": "^4.7.0", 69 | "mocha": "^6.1.4", 70 | "qunit-dom": "^0.9.0", 71 | "request": "^2.88.0" 72 | }, 73 | "engines": { 74 | "node": "8.* || >= 10.*" 75 | }, 76 | "ember-addon": { 77 | "configPath": "tests/dummy/config" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /test/application-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | const denodeify = require('denodeify'); 5 | const request = denodeify(require('request')); 6 | const AddonTestApp = require('ember-cli-addon-tests').AddonTestApp; 7 | 8 | describe('Acceptance | Application', function() { 9 | this.timeout(720000); 10 | 11 | let app; 12 | 13 | before(function() { 14 | app = new AddonTestApp(); 15 | 16 | return app.create('dummy', { 17 | fixturesPath: 'tests' 18 | }).then(() => { 19 | return app.run('npm', 'install'); 20 | }); 21 | }); 22 | 23 | beforeEach(function() { 24 | return app.startServer(); 25 | }); 26 | 27 | afterEach(function() { 28 | return app.stopServer(); 29 | }); 30 | 31 | it('doesn\'t break the build', function() { 32 | return request('http://localhost:49741/assets/vendor.js') 33 | .then(response => { 34 | expect(response.body).to.contain('ember-concurrency-scroll'); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | test_page: 'tests/index.html?hidepassed', 3 | disable_watching: true, 4 | launch_in_ci: [ 5 | 'Chrome' 6 | ], 7 | launch_in_dev: [ 8 | 'Chrome' 9 | ], 10 | browser_args: { 11 | Chrome: { 12 | ci: [ 13 | // --no-sandbox is needed when running Chrome inside a container 14 | process.env.CI ? '--no-sandbox' : null, 15 | '--headless', 16 | '--disable-gpu', 17 | '--disable-dev-shm-usage', 18 | '--disable-software-rasterizer', 19 | '--mute-audio', 20 | '--remote-debugging-port=0', 21 | '--window-size=1440,900' 22 | ].filter(Boolean) 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from './resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from './config/environment'; 5 | 6 | const App = Application.extend({ 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix, 9 | Resolver 10 | }); 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peopleconnectus/ember-concurrency-scroll/a917a525619d3ca4cb8c0da1cfcf4834aa687799/tests/dummy/app/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/components/element-scroll.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { inject as service } from '@ember/service'; 3 | import { task } from 'ember-concurrency'; 4 | export default Component.extend({ 5 | tagName: 'button', 6 | scroller: service(), 7 | scroll: task(function *() { 8 | yield this.get('scroller').scrollToElementId(...arguments); 9 | if (this.get('bounce')) { 10 | yield this.get('scroller').scrollToElementId('1'); 11 | } 12 | }), 13 | click() { 14 | this.get('scroll').perform(this.get('target'), { 15 | duration: 1500, 16 | easeType: 'sin', 17 | ignoreViewport: false 18 | }); 19 | // let cancel = ()=> { 20 | // // console.log('cancel!', this.get('scroller.scrollTo.state')); 21 | // if (this.get('scroller.scrollTo.isRunning')) { 22 | // // scroll back to element 1 23 | // this.get('scroller.scrollToElementId').perform('1'); 24 | // } 25 | // }; 26 | // // this.$(document.body).one('click', cancel); 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peopleconnectus/ember-concurrency-scroll/a917a525619d3ca4cb8c0da1cfcf4834aa687799/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peopleconnectus/ember-concurrency-scroll/a917a525619d3ca4cb8c0da1cfcf4834aa687799/tests/dummy/app/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | -------------------------------------------------------------------------------- /tests/dummy/app/instance-initializers/scroller.js: -------------------------------------------------------------------------------- 1 | export function initialize(appInstance) { 2 | appInstance.inject('controller', 'scroller', 'service:scroller'); 3 | } 4 | 5 | export default { 6 | name: 'scroller', 7 | initialize 8 | }; 9 | -------------------------------------------------------------------------------- /tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peopleconnectus/ember-concurrency-scroll/a917a525619d3ca4cb8c0da1cfcf4834aa687799/tests/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from './config/environment'; 3 | 4 | const Router = EmberRouter.extend({ 5 | location: config.locationType, 6 | rootURL: config.rootURL 7 | }); 8 | 9 | Router.map(function() { 10 | }); 11 | 12 | export default Router; 13 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peopleconnectus/ember-concurrency-scroll/a917a525619d3ca4cb8c0da1cfcf4834aa687799/tests/dummy/app/routes/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- 1 | .block { 2 | dislay: block; 3 | height: 1000px; 4 | width: 1000px; 5 | } 6 | .right { 7 | display: inline-block; 8 | float:right; 9 | } 10 | #portal { 11 | display: block; 12 | overflow: scroll; 13 | height: 100px; 14 | outline: 1px solid black; 15 | } 16 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |

Ember Concurrency Scroll Demo

2 | {{outlet}} 3 |
4 |

ONE

5 | {{#element-scroll target="2"}}two{{/element-scroll}} 6 | {{#element-scroll target="2" bounce=true}}two-one{{/element-scroll}} 7 | 8 | 9 | 10 |

ONE RIGHT

11 | 12 |
13 |
14 |

TWO

15 | {{#element-scroll target="3"}}three{{/element-scroll}} 16 | {{#element-scroll target="1"}}one{{/element-scroll}} 17 |
18 |
19 |

THREE

20 | {{#element-scroll target="4"}}four{{/element-scroll}} 21 | {{#element-scroll target="2"}}two{{/element-scroll}} 22 |
23 |
24 |

FOUR

25 | {{#element-scroll target="5"}}five{{/element-scroll}} 26 | {{#element-scroll target="3"}}three{{/element-scroll}} 27 |
28 |
29 |

FIVE

30 | {{#element-scroll target="6"}}six{{/element-scroll}} 31 | {{#element-scroll target="4"}}four{{/element-scroll}} 32 | {{#element-scroll target="8" bounce=true}}eight-one{{/element-scroll}} 33 |
34 | 35 |
36 |
37 |

SIX

38 |
39 |
40 |

SEVEN

41 |
42 |
43 |

EIGHT

44 |

EIGHT RIGHT

45 |
46 |
47 | 48 | 49 | 50 | {{#element-scroll target="1"}}one{{/element-scroll}} 51 |
52 |

end of line

53 | 54 |
55 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peopleconnectus/ember-concurrency-scroll/a917a525619d3ca4cb8c0da1cfcf4834aa687799/tests/dummy/app/templates/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/templates/components/element-scroll.hbs: -------------------------------------------------------------------------------- 1 | {{yield}} -------------------------------------------------------------------------------- /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 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 13 | }, 14 | EXTEND_PROTOTYPES: { 15 | // Prevent Ember Data from overriding Date.parse. 16 | Date: false 17 | } 18 | }, 19 | 'ember-concurrency-scroll': { 20 | padding: 0 21 | }, 22 | APP: { 23 | // Here you can pass flags/options to your application instance 24 | // when it is created 25 | } 26 | }; 27 | 28 | if (environment === 'development') { 29 | // ENV.APP.LOG_RESOLVER = true; 30 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 31 | // ENV.APP.LOG_TRANSITIONS = true; 32 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 33 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 34 | } 35 | 36 | if (environment === 'test') { 37 | // Testem prefers this... 38 | ENV.locationType = 'none'; 39 | 40 | // keep test console output quieter 41 | ENV.APP.LOG_ACTIVE_GENERATION = false; 42 | ENV.APP.LOG_VIEW_LOOKUPS = false; 43 | 44 | ENV.APP.rootElement = '#ember-testing'; 45 | ENV.APP.autoboot = false; 46 | } 47 | 48 | if (environment === 'production') { 49 | // here you can enable a production-specific feature 50 | } 51 | 52 | return ENV; 53 | }; 54 | -------------------------------------------------------------------------------- /tests/dummy/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "jquery-integration": false 3 | } 4 | -------------------------------------------------------------------------------- /tests/dummy/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions' 7 | ]; 8 | 9 | const isCI = !!process.env.CI; 10 | const isProduction = process.env.EMBER_ENV === 'production'; 11 | 12 | if (isCI || isProduction) { 13 | browsers.push('ie 11'); 14 | } 15 | 16 | module.exports = { 17 | browsers 18 | }; 19 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /tests/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peopleconnectus/ember-concurrency-scroll/a917a525619d3ca4cb8c0da1cfcf4834aa687799/tests/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for "body-footer"}} 31 | {{content-for "test-body-footer"}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/integration/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peopleconnectus/ember-concurrency-scroll/a917a525619d3ca4cb8c0da1cfcf4834aa687799/tests/integration/.gitkeep -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from '../app'; 2 | import config from '../config/environment'; 3 | import { setApplication } from '@ember/test-helpers'; 4 | import { start } from 'ember-qunit'; 5 | 6 | setApplication(Application.create(config.APP)); 7 | 8 | start(); 9 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peopleconnectus/ember-concurrency-scroll/a917a525619d3ca4cb8c0da1cfcf4834aa687799/tests/unit/.gitkeep -------------------------------------------------------------------------------- /tests/unit/services/scroller-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupTest } from 'ember-qunit'; 3 | import window, { reset } from 'ember-window-mock'; 4 | import sinon from 'sinon'; 5 | 6 | module('Unit | Service | scroller', function(hooks) { 7 | setupTest(hooks); 8 | 9 | let scrollToSpy; 10 | let service; 11 | 12 | hooks.beforeEach(function() { 13 | window.scrollTo = scrollToSpy = sinon.spy(); 14 | service = this.owner.factoryFor('service:scroller').create({ 15 | window 16 | }); 17 | }); 18 | 19 | hooks.afterEach(function() { 20 | reset(window); 21 | }); 22 | 23 | test('scrollTo calls window.scrollTo', async function(assert) { 24 | await service.scrollTo(0, 1000); 25 | // default duration of 1000ms should have 100 steps 26 | assert.equal(scrollToSpy.callCount, 100); 27 | assert.deepEqual(scrollToSpy.firstCall.args, [0, 0]); 28 | assert.deepEqual(scrollToSpy.lastCall.args, [0, 1000]); 29 | }); 30 | 31 | test('scrollTo takes object args', async function(assert) { 32 | await service.scrollTo({ x: 0, y: 0 }, { x: 0, y: 1000 }); 33 | 34 | assert.deepEqual(scrollToSpy.firstCall.args, [0, 0]); 35 | assert.deepEqual(scrollToSpy.lastCall.args, [0, 1000]); 36 | }); 37 | 38 | test('scrollTo does scroll x axis if already in viewport', async function(assert) { 39 | await service.scrollTo({ x: 0, y: 0 }, { x: 100, y: 1000 }); 40 | 41 | assert.deepEqual(scrollToSpy.firstCall.args, [0, 0]); 42 | // x axis should not change 43 | assert.deepEqual(scrollToSpy.lastCall.args, [100, 1000]); 44 | }); 45 | 46 | test('scrollTo does not scroll x axis with ignoreViewport', async function(assert) { 47 | await service.scrollTo({ x: 0, y: 0 }, { x: 100, y: 1000 }, { ignoreViewport: false }); 48 | 49 | assert.deepEqual(scrollToSpy.firstCall.args, [0, 0]); 50 | // x axis should not change 51 | assert.deepEqual(scrollToSpy.lastCall.args, [0, 1000]); 52 | }); 53 | 54 | test('scrollTo handles xy scrolling', async function(assert) { 55 | await service.scrollTo({ x: 0, y: 0 }, { x: 5000, y: 5000 }); 56 | 57 | assert.deepEqual(scrollToSpy.firstCall.args, [0, 0]); 58 | assert.deepEqual(scrollToSpy.lastCall.args, [5000, 5000]); 59 | }); 60 | 61 | test('scrollTo handles negative xy scrolling', async function(assert) { 62 | await service.scrollTo({ x: 5000, y: 5000 }, { x: 0, y: 0 }); 63 | 64 | assert.deepEqual(scrollToSpy.firstCall.args, [5000, 5000]); 65 | assert.deepEqual(scrollToSpy.lastCall.args, [0, 0]); 66 | }); 67 | 68 | test('scrollToTask works', async function(assert) { 69 | await service.get('scrollToTask').perform(0, 1000); 70 | // default duration of 1000ms should have 100 steps 71 | assert.equal(scrollToSpy.callCount, 100); 72 | assert.deepEqual(scrollToSpy.firstCall.args, [0, 0]); 73 | assert.deepEqual(scrollToSpy.lastCall.args, [0, 1000]); 74 | }); 75 | // test('scrollToElementId finds element') 76 | }); 77 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peopleconnectus/ember-concurrency-scroll/a917a525619d3ca4cb8c0da1cfcf4834aa687799/vendor/.gitkeep --------------------------------------------------------------------------------