├── app
├── .gitkeep
└── components
│ ├── ember-range-slider.js
│ └── ember-range-slider-handle.js
├── addon
├── .gitkeep
├── components
│ ├── ember-range-slider-handle.js
│ └── ember-range-slider.js
├── templates
│ └── components
│ │ ├── ember-range-slider-handle.hbs
│ │ └── ember-range-slider.hbs
├── styles
│ └── ember-range-slider.css
└── utils
│ └── scale-strategies.js
├── tests
├── unit
│ └── .gitkeep
├── dummy
│ ├── app
│ │ ├── helpers
│ │ │ └── .gitkeep
│ │ ├── models
│ │ │ └── .gitkeep
│ │ ├── routes
│ │ │ └── .gitkeep
│ │ ├── views
│ │ │ └── .gitkeep
│ │ ├── components
│ │ │ ├── .gitkeep
│ │ │ ├── custom-end-handle.js
│ │ │ └── custom-start-handle.js
│ │ ├── controllers
│ │ │ ├── .gitkeep
│ │ │ └── application.js
│ │ ├── templates
│ │ │ ├── components
│ │ │ │ └── .gitkeep
│ │ │ └── application.hbs
│ │ ├── router.js
│ │ ├── app.js
│ │ ├── styles
│ │ │ └── app.css
│ │ └── index.html
│ ├── public
│ │ ├── robots.txt
│ │ └── crossdomain.xml
│ └── config
│ │ └── environment.js
├── test-helper.js
├── helpers
│ ├── resolver.js
│ ├── start-app.js
│ ├── raw-events
│ │ ├── register-acceptance-test-helpers.js
│ │ ├── async.js
│ │ └── utils
│ │ │ └── raw-events.js
│ └── helpers.js
├── .jshintrc
├── index.html
└── acceptance
│ └── range-slider-test.js
├── .watchmanconfig
├── CHANGELOG.md
├── .bowerrc
├── config
├── environment.js
└── ember-try.js
├── .npmignore
├── bower.json
├── .ember-cli
├── .gitignore
├── blueprints
└── ember-range-slider
│ └── index.js
├── testem.json
├── ember-cli-build.js
├── index.js
├── .jshintrc
├── .editorconfig
├── .travis.yml
├── LICENSE.md
├── package.json
└── README.md
/app/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/addon/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/unit/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/helpers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/models/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/routes/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/views/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": ["tmp"]
3 | }
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## 0.1.0
4 |
5 | - Initial implementation
6 |
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components",
3 | "analytics": false
4 | }
5 |
--------------------------------------------------------------------------------
/tests/dummy/public/robots.txt:
--------------------------------------------------------------------------------
1 | # http://www.robotstxt.org
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/app/components/ember-range-slider.js:
--------------------------------------------------------------------------------
1 | export { default } from '@upsilon/ember-range-slider/components/ember-range-slider';
2 |
--------------------------------------------------------------------------------
/config/environment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(/* environment, appConfig */) {
4 | return { };
5 | };
6 |
--------------------------------------------------------------------------------
/app/components/ember-range-slider-handle.js:
--------------------------------------------------------------------------------
1 | export { default } from '@upsilon/ember-range-slider/components/ember-range-slider-handle';
2 |
--------------------------------------------------------------------------------
/tests/test-helper.js:
--------------------------------------------------------------------------------
1 | import resolver from './helpers/resolver';
2 | import { setResolver } from 'ember-qunit';
3 | import { start } from 'ember-cli-qunit';
4 |
5 | setResolver(resolver);
6 | start();
7 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | bower_components/
2 | tests/
3 | tmp/
4 | dist/
5 |
6 | .bowerrc
7 | .editorconfig
8 | .ember-cli
9 | .travis.yml
10 | .npmignore
11 | **/.gitkeep
12 | bower.json
13 | ember-cli-build.js
14 | Brocfile.js
15 | testem.json
16 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/custom-end-handle.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | export default Ember.Component.extend({
4 | classNames: ['CustomHandle', 'CustomHandle--end'],
5 | classNameBindings: ['isSliding'],
6 | attributeBindings: ['style'],
7 | isSliding: false
8 | });
9 |
--------------------------------------------------------------------------------
/addon/components/ember-range-slider-handle.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import layout from '../templates/components/ember-range-slider-handle';
3 |
4 | export default Ember.Component.extend({
5 | layout,
6 | attributeBindings: ['style'],
7 | classNameBindings: ['isSliding']
8 | });
9 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/custom-start-handle.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | export default Ember.Component.extend({
4 | classNames: ['CustomHandle', 'CustomHandle--start'],
5 | classNameBindings: ['isSliding'],
6 | attributeBindings: ['style'],
7 | isSliding: false
8 | });
9 |
--------------------------------------------------------------------------------
/tests/dummy/app/router.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import config from './config/environment';
3 |
4 | var Router = Ember.Router.extend({
5 | location: config.locationType,
6 | rootURL: config.rootURL
7 | });
8 |
9 | Router.map(function() {
10 | });
11 |
12 | export default Router;
13 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-wormhole",
3 | "dependencies": {
4 | "ember": "2.16.2",
5 | "ember-cli-test-loader": "0.2.2",
6 | "ember-qunit": "1.0.0",
7 | "jquery": "3.2.1",
8 | "qunit": "1.23.1"
9 | },
10 | "resolutions": {
11 | "ember": "2.16.2"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/tests/helpers/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from 'ember-resolver';
2 | import config from '../../config/environment';
3 |
4 | var resolver = Resolver.create();
5 |
6 | resolver.namespace = {
7 | modulePrefix: config.modulePrefix,
8 | podModulePrefix: config.podModulePrefix
9 | };
10 |
11 | export default resolver;
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 |
7 | # dependencies
8 | /node_modules
9 | /bower_components
10 |
11 | # misc
12 | /.sass-cache
13 | /connect.lock
14 | /coverage/*
15 | /libpeerconnection.log
16 | npm-debug.log
17 | testem.log
18 |
--------------------------------------------------------------------------------
/blueprints/ember-range-slider/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name: '@upsilon/ember-range-slider',
3 |
4 | normalizeEntityName: function() {},
5 |
6 | afterInstall: function() {
7 | var bowerPackages = [
8 | { name: 'hammerjs', target: '^2.0.8' }
9 | ];
10 |
11 | return this.addPackagesToProject(bowerPackages);
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/testem.json:
--------------------------------------------------------------------------------
1 | {
2 | "framework": "qunit",
3 | "test_page": "tests/index.html?hidepassed&devmode",
4 | "disable_watching": true,
5 | "launch_in_ci": ["Chrome"],
6 | "launch_in_dev": ["Chrome"],
7 | "browser_args": {
8 | "Chrome": [
9 | "--no-sandbox",
10 | "--headless",
11 | "--disable-gpu",
12 | "--remote-debugging-port=9222"
13 | ]
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/addon/templates/components/ember-range-slider-handle.hbs:
--------------------------------------------------------------------------------
1 |
10 |
16 |
17 |
--------------------------------------------------------------------------------
/tests/dummy/app/app.js:
--------------------------------------------------------------------------------
1 | import Ember from "ember";
2 | import Resolver from "ember-resolver";
3 | import loadInitializers from "ember-load-initializers";
4 | import config from "./config/environment";
5 |
6 | var App;
7 |
8 | App = Ember.Application.extend({
9 | modulePrefix: config.modulePrefix,
10 | podModulePrefix: config.podModulePrefix,
11 | Resolver: Resolver,
12 | });
13 |
14 | loadInitializers(App, config.modulePrefix);
15 |
16 | export default App;
17 |
--------------------------------------------------------------------------------
/addon/templates/components/ember-range-slider.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{component startHandleComponentKey
4 | percentage=currentStartPercentage
5 | class=startHandleFullClassName
6 | isSliding=isSlidingStartHandle
7 | style=startHandleStyle}}
8 | {{component endHandleComponentKey
9 | percentage=currentEndPercentage
10 | class=endHandleFullClassName
11 | isSliding=isSlidingEndHandle
12 | style=endHandleStyle}}
13 |
--------------------------------------------------------------------------------
/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | /* global require, module */
2 | var EmberApp = require('ember-cli/lib/broccoli/ember-addon');
3 |
4 | module.exports = function(defaults) {
5 | var app = new EmberApp(defaults, {
6 | snippetSearchPaths: ['tests/dummy/app']
7 | });
8 |
9 | /*
10 | This build file specifes the options for the dummy test app of this
11 | addon, located in `/tests/dummy`
12 | This build file does *not* influence how the addon or the app using it
13 | behave. You most likely want to be modifying `./index.js` or app's build file
14 | */
15 |
16 | return app.toTree();
17 | };
18 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /* jshint node: true */
2 | "use strict";
3 |
4 | const FASTBOOT_TRANSFORMATION_OPTION = {
5 | using: [
6 | {
7 | transformation: 'fastbootShim',
8 | },
9 | ],
10 | };
11 |
12 | module.exports = {
13 | name: "@upsilon/ember-range-slider",
14 |
15 | included(app) {
16 | this._super.included(app);
17 |
18 | let hasFastboot = this.project.findAddonByName('ember-cli-fastboot');
19 | let importOptions = hasFastboot ? FASTBOOT_TRANSFORMATION_OPTION : {};
20 | app.import(
21 | 'node_modules/hammerjs/hammer.js',
22 | importOptions
23 | );
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/tests/dummy/app/styles/app.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Trebuchet', 'Helvetica', sans-serif;
3 | font-size: 14px;
4 | line-height: 1.4em;
5 | }
6 | .example {
7 | border: 1px dotted #ccc;
8 | margin-right: 240px;
9 | margin-top: 50px;
10 | max-width:675px;
11 | padding: 10px 20px;
12 | }
13 | .CustomHandle {
14 | border-radius: 10px;
15 | background-color: green;
16 | height: 20px;
17 | width: 20px;
18 | margin-left: -10px;
19 | }
20 | .CustomHandle--end {
21 | background-color: blue;
22 | border-radius: 0px;
23 | transform: rotate(45deg);
24 | }
25 | .CustomHandle.is-sliding {
26 | box-shadow: 0 0 20px red;
27 | }
28 |
--------------------------------------------------------------------------------
/addon/styles/ember-range-slider.css:
--------------------------------------------------------------------------------
1 | .EmberRangeSlider {
2 | height: 20px;
3 | position: relative;
4 | }
5 |
6 | .EmberRangeSlider-base {
7 | background-color: #d0d0d0;
8 | border-radius: 5px;
9 | height: 4px;
10 | position: absolute;
11 | top: 9px;
12 | width: 100%;
13 | }
14 |
15 | .EmberRangeSlider-active {
16 | background-color: #676767;
17 | height: 4px;
18 | left: 0%; /* dynamic */
19 | position: absolute;
20 | right: 0%; /* dynamic */
21 | top: 9px;
22 | }
23 |
24 | .EmberRangeSlider-handle {
25 | cursor: pointer;
26 | left: 0%; /* dynamic */
27 | margin-left: -5px;
28 | position: absolute;
29 | width: 10px;
30 | }
31 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "document",
4 | "window",
5 | "-Promise"
6 | ],
7 | "browser": true,
8 | "boss": true,
9 | "curly": true,
10 | "debug": false,
11 | "devel": true,
12 | "eqeqeq": true,
13 | "evil": true,
14 | "forin": false,
15 | "immed": false,
16 | "laxbreak": false,
17 | "newcap": true,
18 | "noarg": true,
19 | "noempty": false,
20 | "nonew": false,
21 | "nomen": false,
22 | "onevar": false,
23 | "plusplus": false,
24 | "regexp": false,
25 | "undef": true,
26 | "sub": true,
27 | "strict": false,
28 | "white": false,
29 | "eqnull": true,
30 | "esnext": true,
31 | "unused": true
32 | }
33 |
--------------------------------------------------------------------------------
/tests/dummy/public/crossdomain.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/.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 | [*.js]
17 | indent_style = space
18 | indent_size = 2
19 |
20 | [*.hbs]
21 | insert_final_newline = false
22 | indent_style = space
23 | indent_size = 2
24 |
25 | [*.css]
26 | indent_style = space
27 | indent_size = 2
28 |
29 | [*.html]
30 | indent_style = space
31 | indent_size = 2
32 |
33 | [*.{diff,md}]
34 | trim_trailing_whitespace = false
35 |
--------------------------------------------------------------------------------
/tests/helpers/start-app.js:
--------------------------------------------------------------------------------
1 | import Ember from "ember";
2 | import registerAcceptanceTestHelpers from "./raw-events/register-acceptance-test-helpers";
3 | import Application from "../../app";
4 | import config from "../../config/environment";
5 |
6 | export default function startApp(attrs) {
7 | var application;
8 |
9 | var attributes = Ember.merge({}, config.APP);
10 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override;
11 |
12 | Ember.run(function () {
13 | application = Application.create(attributes);
14 | application.setupForTesting();
15 | registerAcceptanceTestHelpers();
16 | application.injectTestHelpers();
17 | });
18 |
19 | return application;
20 | }
21 |
--------------------------------------------------------------------------------
/tests/helpers/raw-events/register-acceptance-test-helpers.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | import {
4 | triggerTapHelper,
5 | rawTapHelper,
6 | rawPanHelper,
7 | rawMouseDownHelper,
8 | rawMouseUpHelper,
9 | rawMouseMoveHelper,
10 | } from './async';
11 |
12 | export default function(){
13 | Ember.Test.registerAsyncHelper('triggerTap', triggerTapHelper);
14 | Ember.Test.registerAsyncHelper('rawTap', rawTapHelper);
15 | Ember.Test.registerAsyncHelper('rawPan', rawPanHelper);
16 | Ember.Test.registerAsyncHelper('rawMouseDown', rawMouseDownHelper);
17 | Ember.Test.registerAsyncHelper('rawMouseUp', rawMouseUpHelper);
18 | Ember.Test.registerAsyncHelper('rawMouseMove', rawMouseMoveHelper);
19 | }
20 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | language: node_js
3 | node_js:
4 | - "10"
5 |
6 | sudo: false
7 |
8 | cache:
9 | yarn: true
10 | directories:
11 | - node_modules
12 |
13 | env:
14 | - EMBER_TRY_SCENARIO=default
15 | - EMBER_TRY_SCENARIO=2.13.3
16 | - EMBER_TRY_SCENARIO=2.16.2
17 | - EMBER_TRY_SCENARIO=ember-release
18 | - EMBER_TRY_SCENARIO=ember-beta
19 | - EMBER_TRY_SCENARIO=ember-canary
20 |
21 | matrix:
22 | allow_failures:
23 | - env: EMBER_TRY_SCENARIO=ember-beta
24 | - env: EMBER_TRY_SCENARIO=ember-canary
25 |
26 | dist: xenial
27 |
28 | addons:
29 | chrome: stable
30 | apt:
31 | packages:
32 | - chromium-chromedriver
33 |
34 | before_script:
35 | - ln --symbolic /usr/lib/chromium-browser/chromedriver "${HOME}/bin/chromedriver"
36 | - google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost &
37 |
38 | install:
39 | - npm install -g bower
40 | - npm install -g yarn
41 | - yarn install
42 | - bower install
43 |
44 | script:
45 | - ember try:each
46 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015
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 |
--------------------------------------------------------------------------------
/config/ember-try.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 |
3 | module.exports = function () {
4 | return {
5 | useYarn: true,
6 | scenarios: [
7 | {
8 | name: "default",
9 | dependencies: {},
10 | },
11 | {
12 | name: "2.13.3",
13 | dependencies: {
14 | ember: "2.13.3",
15 | },
16 | },
17 | {
18 | name: "2.16.2",
19 | dependencies: {
20 | ember: "2.16.2",
21 | },
22 | },
23 | {
24 | name: "ember-release",
25 | dependencies: {
26 | ember: "components/ember#release",
27 | },
28 | resolutions: {
29 | ember: "release",
30 | },
31 | },
32 | {
33 | name: "ember-beta",
34 | dependencies: {
35 | ember: "components/ember#beta",
36 | },
37 | resolutions: {
38 | ember: "beta",
39 | },
40 | },
41 | {
42 | name: "ember-canary",
43 | dependencies: {
44 | ember: "components/ember#canary",
45 | },
46 | resolutions: {
47 | ember: "canary",
48 | },
49 | },
50 | ],
51 | };
52 | };
53 |
--------------------------------------------------------------------------------
/tests/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "triggerTap",
4 | "rawTap",
5 | "rawPan",
6 | "rawMouseDown",
7 | "rawMouseUp",
8 | "rawMouseMove",
9 | "document",
10 | "window",
11 | "location",
12 | "setTimeout",
13 | "$",
14 | "-Promise",
15 | "define",
16 | "console",
17 | "visit",
18 | "exists",
19 | "fillIn",
20 | "click",
21 | "keyEvent",
22 | "triggerEvent",
23 | "find",
24 | "findWithAssert",
25 | "wait",
26 | "DS",
27 | "andThen",
28 | "currentURL",
29 | "currentPath",
30 | "currentRouteName"
31 | ],
32 | "node": false,
33 | "browser": false,
34 | "boss": true,
35 | "curly": true,
36 | "debug": false,
37 | "devel": false,
38 | "eqeqeq": true,
39 | "evil": true,
40 | "forin": false,
41 | "immed": false,
42 | "laxbreak": false,
43 | "newcap": true,
44 | "noarg": true,
45 | "noempty": false,
46 | "nonew": false,
47 | "nomen": false,
48 | "onevar": false,
49 | "plusplus": false,
50 | "regexp": false,
51 | "undef": true,
52 | "sub": true,
53 | "strict": false,
54 | "white": false,
55 | "eqnull": true,
56 | "esnext": true,
57 | "unused": true
58 | }
59 |
--------------------------------------------------------------------------------
/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 |
21 | {{content-for 'head-footer'}}
22 | {{content-for 'test-head-footer'}}
23 |
24 |
25 |
26 | {{content-for 'body'}}
27 | {{content-for 'test-body'}}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | {{content-for 'body-footer'}}
36 | {{content-for 'test-body-footer'}}
37 |
38 |
39 |
--------------------------------------------------------------------------------
/tests/dummy/config/environment.js:
--------------------------------------------------------------------------------
1 | /* jshint node: true */
2 |
3 | module.exports = function(environment) {
4 | var ENV = {
5 | modulePrefix: 'dummy',
6 | environment: 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. 'with-controller': true
13 | }
14 | },
15 |
16 | APP: {
17 | // Here you can pass flags/options to your application instance
18 | // when it is created
19 | }
20 | };
21 |
22 | if (environment === 'production') {
23 | ENV.rootURL = '/ember-range-slider'; // for gh-pages live demo
24 | }
25 |
26 | if (environment === 'development') {
27 | // ENV.APP.LOG_RESOLVER = true;
28 | // ENV.APP.LOG_ACTIVE_GENERATION = true;
29 | // ENV.APP.LOG_TRANSITIONS = true;
30 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
31 | // ENV.APP.LOG_VIEW_LOOKUPS = true;
32 | }
33 |
34 | if (environment === 'test') {
35 | // Testem prefers this...
36 | ENV.locationType = 'none';
37 |
38 | // keep test console output quieter
39 | ENV.APP.LOG_ACTIVE_GENERATION = false;
40 | ENV.APP.LOG_VIEW_LOOKUPS = false;
41 |
42 | ENV.APP.rootElement = '#ember-testing';
43 | }
44 |
45 | if (environment === 'production') {
46 |
47 | }
48 |
49 | return ENV;
50 | };
51 |
--------------------------------------------------------------------------------
/tests/helpers/raw-events/async.js:
--------------------------------------------------------------------------------
1 | import Ember from "ember";
2 | import {
3 | rawTap,
4 | rawPan,
5 | rawMouseDown,
6 | rawMouseUp,
7 | rawMouseMove
8 | } from './utils/raw-events';
9 |
10 | // Send a tap or a click depending on if we think we're mobile or not.
11 |
12 | export function rawTapHelper(app, parentElement, selector, offsetFromCenter = null) {
13 | Ember.assert('helper must be given a selector', !!selector);
14 | findWithAssert(selector);
15 | rawTap(parentElement, selector, offsetFromCenter);
16 | return app.testHelpers.wait();
17 | }
18 |
19 | export function rawPanHelper(app,parentElement, selector, deltaX, deltaY) {
20 | Ember.assert('helper must be given a selector', !!selector);
21 | findWithAssert(selector);
22 | rawPan(parentElement,selector, deltaX, deltaY);
23 | return app.testHelpers.wait();
24 | }
25 |
26 | export function rawMouseDownHelper(app,parentElement, selector) {
27 | Ember.assert('helper must be given a selector', !!selector);
28 | findWithAssert(selector);
29 | rawMouseDown(parentElement,selector);
30 | return app.testHelpers.wait();
31 | }
32 |
33 | export function rawMouseUpHelper(app, parentElement, selector) {
34 | Ember.assert('helper must be given a selector', !!selector);
35 | findWithAssert(selector);
36 | rawMouseUp(parentElement, selector);
37 | return app.testHelpers.wait();
38 | }
39 |
40 | export function rawMouseMoveHelper(app, parentElement, selector, deltaX, deltaY) {
41 | Ember.assert('helper must be given a selector', !!selector);
42 | findWithAssert(selector);
43 | rawMouseMove(parentElement, selector, deltaX, deltaY);
44 | return app.testHelpers.wait();
45 | }
46 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@upsilon/ember-range-slider",
3 | "version": "1.1.0",
4 | "description": "A horizontal slider component with two handles that allows for visually selecting a range from within a specified min and max.",
5 | "directories": {
6 | "doc": "doc",
7 | "test": "tests"
8 | },
9 | "scripts": {
10 | "start": "ember server",
11 | "build": "ember build",
12 | "test": "ember test"
13 | },
14 | "repository": "https://github.com/collectrium/ember-range-slider.git",
15 | "author": "Yapp Labs",
16 | "license": "MIT",
17 | "devDependencies": {
18 | "broccoli-asset-rev": "^3.0.0",
19 | "ember-cli": "3.1.3",
20 | "ember-cli-app-version": "3.1.0",
21 | "ember-cli-dependency-checker": "2.0.1",
22 | "ember-cli-github-pages": "0.2.0",
23 | "ember-cli-ic-ajax": "0.2.5",
24 | "ember-cli-inject-live-reload": "2.1.0",
25 | "ember-cli-qunit": "4.0.0",
26 | "ember-cli-release": "0.2.9",
27 | "ember-cli-uglify": "2.0.0",
28 | "ember-code-snippet": "^2.0.0",
29 | "ember-disable-prototype-extensions": "1.1.2",
30 | "ember-disable-proxy-controllers": "1.0.2",
31 | "ember-export-application-global": "2.0.0",
32 | "ember-load-initializers": "^2.1.2",
33 | "ember-try": "1.4.0"
34 | },
35 | "keywords": [
36 | "ember-addon"
37 | ],
38 | "dependencies": {
39 | "bower": "^1.8.12",
40 | "ember-cli-babel": "^7.26.6",
41 | "ember-cli-htmlbars": "2.0.2",
42 | "ember-cli-shims": "1.1.0",
43 | "ember-cli-test-loader": "^3.0.0",
44 | "ember-one-way-controls": "^3.1.0",
45 | "ember-resolver": "4.2.4",
46 | "hammerjs": "^2.0.8",
47 | "loader.js": "^4.7.0",
48 | "qunit-notifications": "^1.0.0"
49 | },
50 | "ember-addon": {
51 | "configPath": "tests/dummy/config",
52 | "demoURL": "http://collectrium.github.io/ember-range-slider/"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/application.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | export default Ember.Controller.extend({
4 | example1RangeStart: 3000,
5 | example1RangeEnd: 6000,
6 |
7 | example2RangeStart: 3000,
8 | example2RangeEnd: 6000,
9 | example2IsSliding: false,
10 |
11 | example3RangeStart: 25,
12 | example3RangeEnd: 75,
13 |
14 | example4RangeStart: 3000,
15 | example4RangeEnd: 6000,
16 |
17 | example5RangeStart: -100,
18 | example5RangeEnd: 100000,
19 |
20 | example6RangeStart: 3000,
21 | example6RangeEnd: 6000,
22 |
23 | actions: {
24 | example1RangeSliderChanging(range) {
25 | this.set('example1RangeStart', range.start);
26 | this.set('example1RangeEnd', range.end);
27 | },
28 | example2RangeSliderChanged(range) {
29 | this.set('example2RangeStart', range.start);
30 | this.set('example2RangeEnd', range.end);
31 | },
32 | example3RangeSliderChanging(range) {
33 | this.set('example3RangeStart', range.start);
34 | this.set('example3RangeEnd', range.end);
35 | },
36 | example4RangeSliderChanging(range) {
37 | this.set('example4RangeStart', Math.round(range.start));
38 | this.set('example4RangeEnd', Math.round(range.end));
39 | },
40 | example5RangeSliderChanging(range) {
41 | this.set('example5RangeStart', Math.round(range.start));
42 | this.set('example5RangeEnd', Math.round(range.end));
43 | },
44 | example6RangeSliderChanging(range) {
45 | this.set('example6RangeStart', Math.round(range.start));
46 | this.set('example6RangeEnd', Math.round(range.end));
47 | },
48 | example4UpdateStart(val) {
49 | const endRange = this.get('example4RangeEnd')
50 |
51 | if (val >= 1200 && val <= endRange) {
52 | this.set('example4RangeStart', Math.min(Math.max(1200, val), endRange));
53 | }
54 | },
55 | example4UpdateEnd(val) {
56 | const startRange = this.get('example4RangeStart')
57 |
58 | if (val >= startRange) {
59 | this.set('example4RangeEnd', Math.min(Math.max(val, startRange), 8000));
60 | }
61 | },
62 | example6RoundingHandle(val) {
63 | return parseInt((val / 1000)) * 1000;
64 | }
65 | }
66 | });
67 |
--------------------------------------------------------------------------------
/tests/helpers/helpers.js:
--------------------------------------------------------------------------------
1 | import Ember from "ember";
2 |
3 | const coordinates = (scope) => {
4 | return {
5 | emberRangeSliderWidth: Ember.$(scope)
6 | .find(".EmberRangeSlider")
7 | .css("width")
8 | .replace("px", ""),
9 | emberRangeSliderStartLeft: Ember.$(scope)
10 | .find(".EmberRangeSlider-handle--start")
11 | .css("left")
12 | .replace("px", ""),
13 | emberRangeSliderEndLeft: Ember.$(scope)
14 | .find(".EmberRangeSlider-handle--end")
15 | .css("left")
16 | .replace("px", ""),
17 | emberRangeSliderActiveWidth: Ember.$(scope)
18 | .find(".EmberRangeSlider-active")
19 | .css("width")
20 | .replace("px", ""),
21 | emberRangeSliderActiveLeft: Ember.$(scope)
22 | .find(".EmberRangeSlider-active")
23 | .css("left")
24 | .replace("px", ""),
25 | };
26 | };
27 |
28 | export function startHandlePositionRounded(scope) {
29 | return Math.round(
30 | (+coordinates(scope).emberRangeSliderStartLeft /
31 | +coordinates(scope).emberRangeSliderWidth) *
32 | 100
33 | );
34 | }
35 |
36 | export function endHandlePositionRounded(scope) {
37 | return Math.round(
38 | (+coordinates(scope).emberRangeSliderEndLeft /
39 | +coordinates(scope).emberRangeSliderWidth) *
40 | 100
41 | );
42 | }
43 |
44 | export function activeRangeRounded(scope) {
45 | let left = +coordinates(scope).emberRangeSliderActiveLeft;
46 | let width = +coordinates(scope).emberRangeSliderActiveWidth;
47 | return {
48 | start: Math.round((left / +coordinates(scope).emberRangeSliderWidth) * 100),
49 | end: Math.round(
50 | ((left + width) / +coordinates(scope).emberRangeSliderWidth) * 100
51 | ),
52 | };
53 | }
54 |
55 | export function expectWithin(assert, expected, actual, tolerance, description) {
56 | assert.ok(
57 | Math.abs(expected - actual) <= tolerance,
58 | `Expected ${description} to be within ${tolerance} of ${expected} but was ${actual}`
59 | );
60 | }
61 |
62 | export function boundStartTextValue(scope) {
63 | let text = find(scope).text();
64 | let match = /start: ([0-9.]+)/.exec(text);
65 | return parseFloat(match[1]);
66 | }
67 |
68 | export function boundEndTextValue(scope) {
69 | let text = find(scope).text();
70 | let match = /end: ([0-9.]+)/.exec(text);
71 | return parseFloat(match[1]);
72 | }
73 |
74 | export function boundStartInputValue(scope) {
75 | let value = find(scope).find("input:eq(0)").val();
76 | return parseFloat(value);
77 | }
78 |
79 | export function boundEndInputValue(scope) {
80 | let value = find(scope).find("input:eq(1)").val();
81 | return parseFloat(value);
82 | }
83 |
84 | export function expectIsSliding(assert, scope, expectedText, description) {
85 | let text = find(scope).text();
86 | let match = /isSliding: (\w+)/.exec(text);
87 | assert.equal(
88 | match[1],
89 | expectedText,
90 | `Expected ${description} isSliding to be ${expectedText} but was ${match[1]}`
91 | );
92 | }
93 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ember Range Slider [](https://travis-ci.org/collectrium/ember-range-slider) [](http://emberobserver.com/addons/ember-range-slider)
2 |
3 | Provides a horizontal slider component with two handles that allows for visually selecting a range from within a specified min and max.
4 |
5 | 
6 |
7 | Supported interactions include pressing (or touching) and dragging either handle, as well as tapping on the slider track to adjust the nearest slider handle to the new location.
8 |
9 | The component follows a data down / actions up approach and uses hammer.js to handle the touch and mouse interactions.
10 |
11 | ## Live Demo
12 |
13 | View a live demo here: [http://collectrium.github.io/ember-range-slider/](http://collectrium.github.io/ember-range-slider/)
14 |
15 | ## Example
16 |
17 | ```hbs
18 | {{ember-range-slider
19 | min=rangeMin
20 | max=rangeMax
21 | start=start
22 | end=end
23 | rangeChanging=(action 'rangeSliderChanging')
24 | rangeChanged=(action 'rangeChanged')
25 | roundingHandler=(action 'roundingNearestInteger')
26 | isSlidingChanged=(action (mut isSliding))
27 | }}
28 | ```
29 |
30 | ## Using
31 |
32 | import EmberRangeSlider from '@upsilon/ember-range-slider/components/ember-range-slider';
33 |
34 | ## Data down
35 |
36 | `min` and `max` are numeric values that define the values associated with the left and right edge of the slider.
37 |
38 | `start` and `end` are numeric values assigned to the left and right handle.
39 |
40 | ## Actions Up
41 |
42 | The `rangeChanging` action is fired with start and end values continuously as the user slides a handle along the range.
43 |
44 | The `rangeChanged` action is fired with start and end values when a range change is "committed", i.e. when the user releases the slider handle.
45 |
46 | The `isSlidingChanged` action is fired with `true` as the user begins sliding a handle and again with `false` when the user releases the handle.
47 |
48 | The `roundingHandler` action is executed with a start and end value, continuously enclosing it in a rounding function and returning a new value as the user moves the handle over the range.
49 |
50 | ## Development Setup
51 |
52 | ### Installation
53 |
54 | * `git clone` this repository
55 | * `yarn install`
56 | * `bower install`
57 |
58 | ### Running Tests
59 |
60 | * `ember try:each`
61 | * `ember test`
62 | * `ember test --server`
63 |
64 | ### Running the dummy app
65 |
66 | * `ember server`
67 | * Visit your app at http://localhost:4200.
68 |
69 | For more information on using ember-cli, visit [http://www.ember-cli.com/](http://www.ember-cli.com/).
70 |
71 | ## Credits
72 |
73 | This addon was extracted from a prototype project at Collectrium. Contributions from @lukemelia, @chrislopresto and @leahdungo.
74 |
--------------------------------------------------------------------------------
/addon/utils/scale-strategies.js:
--------------------------------------------------------------------------------
1 | const { abs, round, floor, log10, pow } = Math;
2 |
3 | const scaleStrategies = {
4 |
5 | linear: {
6 | getPercentage(min, max, value) {
7 | return (value - min) / (max - min) * 100;
8 | },
9 |
10 | getValue(min, max, percentage) {
11 | return (max - min) * (percentage / 100) + min;
12 | },
13 | },
14 |
15 | logarithmic: {
16 | getPercentage(_min, _max, _value) {
17 | const max = _max || 1;
18 | const min = _min || 1;
19 | const value = _value || 1;
20 |
21 | if (min >= 0 && max >= 0) {
22 | const power = log10(value / min);
23 | const range = log10(max / min);
24 | const closestIntegerPower = floor(power);
25 |
26 | return 100 * (closestIntegerPower / range) + 100 * (power % 1 / range);
27 | } else if (min < 0 && max >= 0) {
28 | const absoluteMin = abs(min);
29 | const absoluteMax = abs(max);
30 | const distance = absoluteMin + absoluteMax;
31 | const zeroPointCoefficient = absoluteMin / distance;
32 | const zeroPointPercentage = zeroPointCoefficient * 100;
33 |
34 | if (value >= 0) {
35 | const valueAtRightHalf = 100 * (log10(value) / log10(max)) * (max / distance);
36 | return zeroPointPercentage + valueAtRightHalf;
37 | } else {
38 | const progress = log10(-value) / log10(absoluteMin);
39 | const minPartProportion = (absoluteMin / distance);
40 | const valueAtLeftHalf = 100 * progress * minPartProportion;
41 | return zeroPointPercentage - valueAtLeftHalf;
42 | }
43 | } else {
44 | return 100 - scaleStrategies.logarithmic.getPercentage(-min, -max, -value);
45 | }
46 | },
47 |
48 | getValue(_min, _max, percentage) {
49 | const max = _max || 1;
50 | const min = _min || 1;
51 |
52 | if (min >= 0 && max >= 0) {
53 | const range = log10(max / min);
54 | const offset = log10(min);
55 | const power = range * (percentage / 100) + offset;
56 | const closestIntegerPower = floor(power);
57 |
58 | const value = pow(10, power);
59 | return round(value / pow(10, closestIntegerPower)) * pow(10, closestIntegerPower);
60 | } else if (min < 0 && max >= 0) {
61 | const absoluteMin = abs(min);
62 | const absoluteMax = abs(max);
63 | const distance = absoluteMin + absoluteMax;
64 | const zeroPointCoefficient = absoluteMin / distance;
65 | const zeroPointPercentage = zeroPointCoefficient * 100;
66 |
67 | const absolutePercentage = percentage - zeroPointPercentage;
68 | if (absolutePercentage >= 0) {
69 | let relativePercentage = absolutePercentage / (absoluteMax / distance);
70 | return scaleStrategies.logarithmic.getValue(0, absoluteMax, relativePercentage);
71 | } else {
72 | let relativePercentage = -absolutePercentage / (absoluteMin / distance);
73 | return -scaleStrategies.logarithmic.getValue(0, absoluteMin, relativePercentage);
74 | }
75 | } else {
76 | return -scaleStrategies.logarithmic.getValue(-max, -min, percentage);
77 | }
78 | },
79 | },
80 |
81 | };
82 |
83 | export default scaleStrategies;
84 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/application.hbs:
--------------------------------------------------------------------------------
1 | ember-range-slider
2 |
3 | {{outlet}}
4 |
5 |
6 |
Example using "rangeChanging" action, which fires continuously as sliders move.
7 | {{code-snippet name='example1.hbs'}}
8 | {{!-- BEGIN-SNIPPET example1 --}}
9 | {{ember-range-slider
10 | min=1200
11 | max=8000
12 | start=example1RangeStart
13 | end=example1RangeEnd
14 | rangeChanging=(action 'example1RangeSliderChanging')
15 | }}
16 | {{!-- END-SNIPPET --}}
17 |
18 | start: {{example1RangeStart}}
19 | end: {{example1RangeEnd}}
20 |
21 |
22 |
23 |
24 |
Example using "rangeChanged" action, which fires once slider is released.
25 | This example also demonstrated the "isSlidingChanged" action, which fires
26 | when the user begins sliding with false, and when then complete sliding,
27 | with true.
28 | {{code-snippet name='example2.hbs'}}
29 | {{!-- BEGIN-SNIPPET example2 --}}
30 | {{ember-range-slider
31 | min=1200
32 | max=8000
33 | start=example2RangeStart
34 | end=example2RangeEnd
35 | rangeChanged=(action 'example2RangeSliderChanged')
36 | isSlidingChanged=(action (mut example2IsSliding))
37 | }}
38 | {{!-- END-SNIPPET --}}
39 |
40 | start: {{example2RangeStart}}
41 | end: {{example2RangeEnd}}
42 | isSliding: {{example2IsSliding}}
43 |
44 |
45 |
46 |
47 |
Example of supplying your own component for the slider handles.
48 | {{code-snippet name='example3.hbs'}}
49 | {{!-- BEGIN-SNIPPET example3 --}}
50 | {{ember-range-slider
51 | min=0
52 | max=100
53 | start=example3RangeStart
54 | end=example3RangeEnd
55 | startHandleComponentKey="custom-start-handle"
56 | endHandleComponentKey="custom-end-handle"
57 | rangeChanging=(action 'example3RangeSliderChanging')
58 | }}
59 | {{!-- END-SNIPPET --}}
60 |
61 | start: {{example3RangeStart}}
62 | end: {{example3RangeEnd}}
63 |
64 |
65 |
66 |
67 |
Example of text fields working in concert with slider. Press enter after editing a text field value.
68 | {{code-snippet name='example4.hbs'}}
69 | {{!-- BEGIN-SNIPPET example4 --}}
70 | {{ember-range-slider
71 | min=1200
72 | max=8000
73 | start=example4RangeStart
74 | end=example4RangeEnd
75 | rangeChanging=(action 'example4RangeSliderChanging')
76 | }}
77 |
78 |
79 | -
80 |
81 |
82 |
83 | {{!-- END-SNIPPET --}}
84 |
85 | start: {{example4RangeStart}}
86 | end: {{example4RangeEnd}}
87 |
88 |
89 |
90 |
91 |
You can specify a 'scale' property to set a non-linear scale.
92 | {{code-snippet name='example1.hbs'}}
93 | {{!-- BEGIN-SNIPPET example1 --}}
94 | {{ember-range-slider
95 | min=-1000000
96 | max=1000000
97 | start=example5RangeStart
98 | end=example5RangeEnd
99 | scale='logarithmic'
100 | rangeChanging=(action 'example5RangeSliderChanging')
101 | }}
102 | {{!-- END-SNIPPET --}}
103 |
104 | start: {{example5RangeStart}}
105 | end: {{example5RangeEnd}}
106 |
107 |
108 |
109 |
110 |
Example using "roundingHandler" action to pass a rounding function.
111 | {{code-snippet name='example6.hbs'}}
112 | {{!-- BEGIN-SNIPPET example6 --}}
113 | {{ember-range-slider
114 | min=1000
115 | max=10000
116 | start=example6RangeStart
117 | end=example6RangeEnd
118 | rangeChanging=(action 'example6RangeSliderChanging')
119 | roundingHandler=(action 'example6RoundingHandle')
120 | }}
121 | {{!-- END-SNIPPET --}}
122 |
123 | start: {{example6RangeStart}}
124 | end: {{example6RangeEnd}}
125 |
126 |
127 |
--------------------------------------------------------------------------------
/tests/helpers/raw-events/utils/raw-events.js:
--------------------------------------------------------------------------------
1 | /* globals PointerEvent */
2 |
3 | import Ember from "ember";
4 |
5 | function createTouchEvent(name, x, y /*, identifier*/) {
6 | const event = new PointerEvent(name, {
7 | view: window,
8 | bubbles: true,
9 | cancelable: true,
10 | button: 0,
11 | clientX: x,
12 | clientY: y,
13 | screenX: x,
14 | screenY: y,
15 | });
16 | return event;
17 | }
18 |
19 | function dispatchTouchEvent(el, name, x, y) {
20 | const event = createTouchEvent(name, x, y);
21 | el.dispatchEvent(event);
22 | }
23 |
24 | function elementCoordinates($element, $parentElement) {
25 | const width = $element.width();
26 | const height = $element.height();
27 | const offset = $element.offset();
28 | const sliderWidth = +$parentElement.css("width").replace("px", "");
29 | return {
30 | x: offset.left + width / 2 - 0.4,
31 | y: offset.top + height / 2,
32 | sliderWidth,
33 | };
34 | }
35 |
36 | export function rawTap(parentElement, selector, offsetFromCenter = null) {
37 | return new Ember.RSVP.Promise(function (resolve) {
38 | const $parent = Ember.$(parentElement);
39 | const $element = Ember.$(selector);
40 | const center = elementCoordinates($element, $parent);
41 | if (offsetFromCenter) {
42 | center.x = center.x + (center.sliderWidth * offsetFromCenter.x) / 100;
43 | center.y = center.y + offsetFromCenter.y;
44 | }
45 | dispatchTouchEvent($element[0], "pointerdown", center.x, center.y);
46 | dispatchTouchEvent($element[0], "pointerup", center.x, center.y);
47 | Ember.run.later(() => {
48 | resolve();
49 | }, 100);
50 | });
51 | }
52 |
53 | export function rawPan(parentElement, selector, deltaX, deltaY) {
54 | return new Ember.RSVP.Promise(function (resolve) {
55 | const $parent = Ember.$(parentElement);
56 | const $element = Ember.$(selector);
57 | const begin = elementCoordinates($element, $parent);
58 | const end = {
59 | x: begin.x + (begin.sliderWidth * deltaX) / 100,
60 | y: begin.y + deltaY,
61 | };
62 |
63 | dispatchTouchEvent($element[0], "pointerdown", begin.x, begin.y);
64 | dispatchTouchEvent($element[0], "pointermove", begin.x, begin.y);
65 |
66 | Ember.run.later(() => {
67 | dispatchTouchEvent($element[0], "pointermove", end.x, end.y);
68 |
69 | Ember.run.later(() => {
70 | dispatchTouchEvent($element[0], "pointermove", end.x, end.y);
71 | dispatchTouchEvent($element[0], "pointerup", end.x, end.y);
72 |
73 | Ember.run.later(() => {
74 | resolve();
75 | }, 100);
76 | }, 100);
77 | }, 100);
78 | });
79 | }
80 |
81 | export function rawMouseDown(parentElement, selector) {
82 | return new Ember.RSVP.Promise(function (resolve) {
83 | const $parent = Ember.$(parentElement);
84 | const $element = Ember.$(selector);
85 | const coords = elementCoordinates($element, $parent);
86 | dispatchTouchEvent($element[0], "pointerdown", coords.x, coords.y);
87 | Ember.run.later(() => {
88 | resolve();
89 | }, 100);
90 | });
91 | }
92 |
93 | export function rawMouseUp(parentElement, selector) {
94 | return new Ember.RSVP.Promise(function (resolve) {
95 | const $parent = Ember.$(parentElement);
96 | const $element = Ember.$(selector);
97 | const coords = elementCoordinates($element, $parent);
98 | dispatchTouchEvent($element[0], "pointerup", coords.x, coords.y);
99 | Ember.run.later(() => {
100 | resolve();
101 | }, 100);
102 | });
103 | }
104 |
105 | export function rawMouseMove(parentElement, selector, deltaX, deltaY) {
106 | return new Ember.RSVP.Promise(function (resolve) {
107 | const $parent = Ember.$(parentElement);
108 | const $element = Ember.$(selector);
109 | const begin = elementCoordinates($element, $parent);
110 | const end = {
111 | x: begin.x + (begin.sliderWidth * deltaX) / 100,
112 | y: begin.y + deltaY,
113 | };
114 |
115 | dispatchTouchEvent($element[0], "pointermove", begin.x, begin.y);
116 | Ember.run.later(() => {
117 | dispatchTouchEvent($element[0], "pointermove", end.x, end.y);
118 | Ember.run.later(() => {
119 | resolve();
120 | }, 100);
121 | }, 100);
122 | });
123 | }
124 |
--------------------------------------------------------------------------------
/addon/components/ember-range-slider.js:
--------------------------------------------------------------------------------
1 | /* global Hammer */
2 | import Ember from 'ember';
3 | import layout from '../templates/components/ember-range-slider';
4 | import scaleStrategies from '../utils/scale-strategies';
5 |
6 | const { computed, isBlank, run, get, set } = Ember;
7 | const { htmlSafe } = Ember.String;
8 |
9 | function constrainToBetween(value, min, max) {
10 | value = Math.max(min, value);
11 | return Math.min(value, max);
12 | }
13 |
14 | export default Ember.Component.extend({
15 | layout,
16 | classNames: ['EmberRangeSlider'],
17 |
18 | /* Required input properties. These are not updated by this component per the
19 | * "data-down" portion of the data-down / actions-up approach.
20 | */
21 | min: null,
22 | max: null,
23 | start: null,
24 | end: null,
25 |
26 | scale: 'linear',
27 | scaleStrategies,
28 |
29 | roundingHandler: ( value ) => value,
30 | onlyIntegers: false,
31 |
32 | baseClassName: 'EmberRangeSlider-base',
33 | activeRegionClassName: 'EmberRangeSlider-active',
34 | handleClassName: 'EmberRangeSlider-handle',
35 | startHandleClassName: 'EmberRangeSlider-handle--start',
36 | endHandleClassName: 'EmberRangeSlider-handle--end',
37 |
38 | startHandleFullClassName: computed('handleClassName', 'startHandleClassName', function () {
39 | return `${this.get('handleClassName')} ${this.get('startHandleClassName')}`;
40 | }),
41 |
42 | endHandleFullClassName: computed('handleClassName', 'endHandleClassName', function () {
43 | return `${this.get('handleClassName')} ${this.get('endHandleClassName')}`;
44 | }),
45 |
46 | /* Set these properties to use another component for the
47 | * start and/or end slider handles
48 | */
49 | startHandleComponentKey: 'ember-range-slider-handle',
50 | endHandleComponentKey: 'ember-range-slider-handle',
51 |
52 | /* These properties are bound down to the handle components
53 | * so that they are aware when they are sliding.
54 | */
55 | isSlidingStartHandle: false,
56 | isSlidingEndHandle: false,
57 |
58 | /* Handle positioning is done based on percentages, and the startPercentage
59 | * and endPercentage CPs are responsible for converting between the provided
60 | * min/max/start/end values and the percentages used for layout. Their
61 | * setters also invoke the rangeChanging action, which should be used for
62 | * providing immediate feedback.
63 | */
64 | startPercentage: computed('min', 'max', 'start', {
65 | get() {
66 | const scale = get(this, 'scale');
67 | const strategy = get(this, 'scaleStrategies')[scale];
68 |
69 | const min = get(this, 'min');
70 | const max = get(this, 'max');
71 | const start = get(this, 'start');
72 | const value = isBlank(start) ? min : start;
73 |
74 | const percentage = strategy.getPercentage(min, max, value);
75 | const limitedPercentage = constrainToBetween(percentage, 0, 100);
76 |
77 | if (!get(this, 'isSlidingStartHandle')) {
78 | set(this, 'mockStartPercentage', limitedPercentage);
79 | }
80 |
81 | return limitedPercentage;
82 | },
83 | set(key, percentage) {
84 | const updatedStart = this.getValueFromPercentage(percentage);
85 | set(this, 'mockStartPercentage', percentage);
86 | this.sendAction('rangeChanging', {
87 | start: updatedStart,
88 | end: get(this, 'end')
89 | });
90 | return percentage;
91 | }
92 | }),
93 | endPercentage: computed('min', 'max', 'end', {
94 | get() {
95 | const scale = get(this, 'scale');
96 | const strategy = get(this, 'scaleStrategies')[scale];
97 |
98 | const min = get(this, 'min');
99 | const max = get(this, 'max');
100 | const end = get(this, 'end');
101 | const value = isBlank(end) ? max : end;
102 |
103 | const percentage = strategy.getPercentage(min, max, value);
104 | const limitedPercentage = constrainToBetween(percentage, 0, 100);
105 |
106 | if (!get(this, 'isSlidingEndHandle')) {
107 | set(this, 'mockEndPercentage', limitedPercentage);
108 | }
109 |
110 | return limitedPercentage;
111 | },
112 | set(key, percentage) {
113 | const updatedEnd = this.getValueFromPercentage(percentage);
114 | set(this, 'mockEndPercentage', percentage);
115 | this.sendAction('rangeChanging', {
116 | start: get(this, 'start'),
117 | end: updatedEnd
118 | });
119 | return percentage;
120 | }
121 | }),
122 |
123 | /**
124 | * These values is showing when handle is sliding.
125 | * Used to prevent yanking of handle.
126 | */
127 | mockStartPercentage: null,
128 | mockEndPercentage: null,
129 |
130 | currentStartPercentage: computed('isSlidingStartHandle', 'startPercentage', 'mockStartPercentage', function() {
131 | const isSlidingStartHandle = get(this, 'isSlidingStartHandle');
132 |
133 | if (!isSlidingStartHandle) {
134 | const startPercentage = get(this, 'startPercentage');
135 | set(this, 'mockStartPercentage', startPercentage);
136 | return startPercentage;
137 | } else {
138 | return get(this, 'mockStartPercentage');
139 | }
140 | }),
141 | currentEndPercentage: computed('isSlidingEndHandle', 'endPercentage', 'mockEndPercentage', function() {
142 | const isSlidingEndHandle = get(this, 'isSlidingEndHandle');
143 |
144 | if (!isSlidingEndHandle) {
145 | const endPercentage = get(this, 'endPercentage');
146 | set(this, 'mockEndPercentage', endPercentage);
147 | return endPercentage;
148 | } else {
149 | return get(this, 'mockEndPercentage');
150 | }
151 | }),
152 |
153 | /* These three CPs are used for dynamic binding in the handlebars template.
154 | */
155 | activeRangeStyle: computed('currentStartPercentage', 'currentEndPercentage', function() {
156 | let startPercentage = this.get('currentStartPercentage');
157 | let endPercentage = this.get('currentEndPercentage');
158 | return htmlSafe(`left: ${startPercentage}%; right: ${100 - endPercentage}%`);
159 | }),
160 | startHandleStyle: computed('currentStartPercentage', function() {
161 | let startPercentage = this.get('currentStartPercentage'),
162 | endPercentage = this.get('currentEndPercentage');
163 | startPercentage = Math.round(startPercentage * 1000) / 1000;
164 | endPercentage = Math.round(endPercentage * 1000) / 1000;
165 | if (+startPercentage === 100 && +endPercentage === 100) {
166 | return htmlSafe(`left: ${startPercentage}%; z-index: 1`);
167 | } else {
168 | return htmlSafe(`left: ${startPercentage}%`);
169 | }
170 | }),
171 | endHandleStyle: computed('currentEndPercentage', function() {
172 | let endPercentage = this.get('currentEndPercentage'),
173 | startPercentage = this.get('currentStartPercentage');
174 | startPercentage = Math.round(startPercentage * 1000) / 1000;
175 | endPercentage = Math.round(endPercentage * 1000) / 1000;
176 | if (+startPercentage === 0 && +endPercentage === 0) {
177 | return htmlSafe(`left: ${endPercentage}%; z-index: 1`);
178 | } else {
179 | return htmlSafe(`left: ${endPercentage}%`);
180 | }
181 | }),
182 |
183 | didInsertElement() {
184 | this._super(...arguments);
185 | this.setupHammerEvents();
186 | },
187 |
188 | /* Set up a Hammer.js manager to handle touch and mouse events.
189 | * Hammer is a global that is available by virtue of this plugin
190 | * app.import'ing the hammer.js bower_component
191 | */
192 | setupHammerEvents() {
193 | this._hammer = new Hammer.Manager(this.get('element'), {
194 | recognizers: [
195 | [Hammer.Tap],
196 | [Hammer.Press, { time: 1 }],
197 | [Hammer.Pan, { direction: Hammer.DIRECTION_HORIZONTAL, threshold: 1 }]
198 | ]
199 | });
200 | this.wirePress();
201 | this.wirePan();
202 | this.wireTap();
203 | },
204 |
205 | /* Tear down the Hammer.js manager on destroy.
206 | */
207 | willDestroyElement() {
208 | this._super(...arguments);
209 | this._hammer.destroy();
210 | },
211 |
212 | /* This next set of methods contain all the references to css class names
213 | * for this component. If you subclass and provide your own template with
214 | * different class names, override these methods.
215 | */
216 | getSliderHandleFromEventTarget(eventTarget) {
217 | return Ember.$(eventTarget).closest(`.${this.get('handleClassName')}`);
218 | },
219 |
220 | isStartHandle($sliderHandle) {
221 | return $sliderHandle.hasClass(this.get('startHandleClassName'));
222 | },
223 |
224 | isEndHandle($sliderHandle) {
225 | return $sliderHandle.hasClass(this.get('endHandleClassName'));
226 | },
227 |
228 | isSliderBase($element) {
229 | return $element.hasClass(this.get('baseClassName'));
230 | },
231 |
232 | isSliderActiveRegion($element) {
233 | return $element.hasClass(this.get('activeRegionClassName'));
234 | },
235 |
236 | getPercentageFromX(x) {
237 | let $sliderBase = this.$(`.${this.get('baseClassName')}`);
238 | let baseLeft = $sliderBase.offset().left;
239 | let baseRight = baseLeft + $sliderBase.width();
240 | return ((x - baseLeft) / (baseRight - baseLeft)) * 100;
241 | },
242 |
243 | /* Convert from percentage position to the numeric value in
244 | * terms of the passed in range min and max.
245 | */
246 |
247 | getValueFromPercentage(percentage) {
248 | const min = get(this, 'min');
249 | const max = get(this, 'max');
250 | const onlyIntegers = get(this, 'onlyIntegers');
251 | const roundingHandler = get(this, 'roundingHandler');
252 |
253 | const scale = get(this, 'scale');
254 | const strategy = get(this, 'scaleStrategies')[scale];
255 |
256 | const value = roundingHandler(strategy.getValue(min, max, percentage));
257 | return (onlyIntegers) ? Math.round(value) : value;
258 | },
259 |
260 | /* press events precede pan events, so we can use this to keep track of
261 | * which handle is being interacted with, start or end
262 | */
263 | wirePress() {
264 | this._hammer.on('press', (ev) => {
265 | let $sliderHandle = this.getSliderHandleFromEventTarget(ev.target);
266 | if (this.isStartHandle($sliderHandle)) {
267 | this._isMovingStartHandle = true;
268 | this.startSliding();
269 | }
270 | if (this.isEndHandle($sliderHandle)) {
271 | this._isMovingEndHandle = true;
272 | this.startSliding();
273 | }
274 | });
275 |
276 | this._hammer.on('pressup', () => {
277 | this.stopSliding();
278 | });
279 | },
280 |
281 | /* pan events get translated to a percentage position when a handle is
282 | * being dragged around
283 | */
284 | wirePan() {
285 | this._hammer.on('pan', (ev) => {
286 | if (this._isMovingStartHandle) {
287 | let startHandlePercent = this.getPercentageFromX(ev.center.x);
288 | let endPercentage = this.get('endPercentage');
289 | startHandlePercent = constrainToBetween(startHandlePercent, 0, endPercentage);
290 | run(() => {
291 | this.set('startPercentage', startHandlePercent);
292 | });
293 | }
294 | if (this._isMovingEndHandle) {
295 | let endHandlePercent = this.getPercentageFromX(ev.center.x);
296 | let startPercentage = this.get('startPercentage');
297 | endHandlePercent = constrainToBetween(endHandlePercent, startPercentage, 100);
298 | run(() => {
299 | this.set('endPercentage', endHandlePercent);
300 | });
301 | }
302 | });
303 | this._hammer.on('panend', () => {
304 | this.stopSliding();
305 | });
306 | },
307 |
308 | /* tap events are translated to a percentage position when a handle is
309 | * being dragged around
310 | */
311 | wireTap() {
312 | this._hammer.on('tap', (ev) => {
313 | let $target = Ember.$(ev.target);
314 | let tapShouldPositionHandle = this.isSliderBase($target) || this.isSliderActiveRegion($target);
315 | if (tapShouldPositionHandle) {
316 | let tappablePercent = this.getPercentageFromX(ev.center.x);
317 | let startPercentage = this.get('startPercentage');
318 | let endPercentage = this.get('endPercentage');
319 | let distanceFromStart = Math.abs(tappablePercent - startPercentage);
320 | let distanceFromEnd = Math.abs(tappablePercent - endPercentage);
321 | run(() => {
322 | if (distanceFromStart > distanceFromEnd) {
323 | this.set('endPercentage', tappablePercent);
324 | } else {
325 | this.set('startPercentage', tappablePercent);
326 | }
327 | this.sendRangeChanged();
328 | });
329 | return;
330 | }
331 | if (this.getSliderHandleFromEventTarget(ev.target).length === 1) {
332 | this.stopSliding();
333 | }
334 | });
335 | },
336 |
337 | sendRangeChanged() {
338 | let startPercentage = this.get('startPercentage');
339 | let endPercentage = this.get('endPercentage');
340 | let start = this.getValueFromPercentage(startPercentage);
341 | let end = this.getValueFromPercentage(endPercentage);
342 | this.sendAction('rangeChanged', { start, end });
343 | },
344 |
345 | startSliding() {
346 | run(() => {
347 | this.set('isSlidingStartHandle', this._isMovingStartHandle);
348 | this.set('isSlidingEndHandle', this._isMovingEndHandle);
349 | this.sendAction('isSlidingChanged', true);
350 | });
351 | },
352 |
353 | stopSliding() {
354 | if (!this._isMovingStartHandle && !this._isMovingEndHandle) {
355 | return;
356 | }
357 | this._isMovingStartHandle = false;
358 | this._isMovingEndHandle = false;
359 | run(() => {
360 | this.set('isSlidingStartHandle', false);
361 | this.set('isSlidingEndHandle', false);
362 | this.sendAction('isSlidingChanged', false);
363 | this.sendRangeChanged();
364 | });
365 | }
366 | });
367 |
--------------------------------------------------------------------------------
/tests/acceptance/range-slider-test.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import {
3 | module,
4 | test
5 | } from 'qunit';
6 | import startApp from 'dummy/tests/helpers/start-app';
7 | import {
8 | startHandlePositionRounded,
9 | endHandlePositionRounded,
10 | activeRangeRounded,
11 | expectWithin,
12 | boundStartTextValue,
13 | boundEndTextValue,
14 | boundStartInputValue,
15 | boundEndInputValue,
16 | expectIsSliding
17 | } from '../helpers/helpers';
18 |
19 | var application;
20 |
21 | module('Acceptance: Range Slider', {
22 | beforeEach: function() {
23 | application = startApp();
24 | },
25 |
26 | afterEach: function() {
27 | Ember.run(application, 'destroy');
28 | }
29 | });
30 |
31 | test('initial rendering', function(assert) {
32 | visit('/');
33 | andThen(function() {
34 | assert.equal(currentPath(), 'index');
35 |
36 | expectWithin(assert, 26, startHandlePositionRounded('.example-1'), 1, 'start handle is positioned (Example 1)');
37 | expectWithin(assert, 70, endHandlePositionRounded('.example-1'), 1, 'end handle is positioned (Example 1)');
38 | expectWithin(assert, 26, activeRangeRounded('.example-1').start, 1, 'active range start is positioned (Example 1)');
39 | expectWithin(assert, 70, activeRangeRounded('.example-1').end, 1, 'active range end is positioned (Example 1)');
40 |
41 | expectWithin(assert, 26, startHandlePositionRounded('.example-2'), 1, 'start handle is positioned (Example 2)');
42 | expectWithin(assert, 70, endHandlePositionRounded('.example-2'), 1, 'end handle is positioned (Example 2)');
43 | expectWithin(assert, 26, activeRangeRounded('.example-2').start, 1, 'active range start is positioned (Example 2)');
44 | expectWithin(assert, 70, activeRangeRounded('.example-2').end, 1, 'active range end is positioned (Example 2)');
45 |
46 | expectWithin(assert, 25, startHandlePositionRounded('.example-3'), 1, 'start handle is positioned (Example 3)');
47 | expectWithin(assert, 75, endHandlePositionRounded('.example-3'), 1, 'end handle is positioned (Example 3)');
48 | expectWithin(assert, 25, activeRangeRounded('.example-3').start, 1, 'active range start is positioned (Example 3)');
49 | expectWithin(assert, 75, activeRangeRounded('.example-3').end, 1, 'active range end is positioned (Example 3)');
50 |
51 | expectWithin(assert, 26, startHandlePositionRounded('.example-4'), 1, 'start handle is positioned (Example 4)');
52 | expectWithin(assert, 70, endHandlePositionRounded('.example-4'), 1, 'end handle is positioned (Example 4)');
53 | expectWithin(assert, 26, activeRangeRounded('.example-4').start, 1, 'active range start is positioned (Example 4)');
54 | expectWithin(assert, 70, activeRangeRounded('.example-4').end, 1, 'active range end is positioned (Example 4)');
55 | });
56 | });
57 |
58 | test('slide start handle interaction with rangeChanging action', function(assert) {
59 | visit('/');
60 | andThen(function() {
61 | expectWithin(assert, 26, startHandlePositionRounded('.example-1'), 1, 'before interaction start handle position');
62 | expectWithin(assert, 3000, boundStartTextValue('.example-1'), 1, 'before interaction bound start text');
63 | });
64 | rawMouseDown(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--start");
65 | andThen(function() {
66 | expectWithin(assert, 26, startHandlePositionRounded('.example-1'), 1, 'after mousedown start handle position');
67 | expectWithin(assert, 3000, boundStartTextValue('.example-1'), 50, 'after mousedown bound start text');
68 | });
69 | rawMouseMove(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--start", -4, 0);
70 | andThen(function() {
71 | expectWithin(assert, 23, startHandlePositionRounded('.example-1'), 1, 'after mousemove lower start handle position');
72 | expectWithin(assert, 2765, boundStartTextValue('.example-1'), 50, 'after mousemove lower bound start text');
73 | });
74 | rawMouseMove(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--start", 6, 0);
75 | andThen(function() {
76 | expectWithin(assert, 29, startHandlePositionRounded('.example-1'), 1, 'after mousemove higher start handle position');
77 | expectWithin(assert, 3150, boundStartTextValue('.example-1'), 50, 'after mousemove higher bound start text');
78 | });
79 | rawMouseMove(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--start", 0, -5);
80 | andThen(function() {
81 | expectWithin(assert, 29, startHandlePositionRounded('.example-1'), 1, 'after mousemove vertical start handle position');
82 | expectWithin(assert, 3150, boundStartTextValue('.example-1'), 50, 'after mousemove vertical bound start text');
83 | });
84 | rawMouseUp(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--start");
85 | andThen(function() {
86 | expectWithin(assert, 29, startHandlePositionRounded('.example-1'), 1, 'after mouseup start handle position');
87 | expectWithin(assert, 3150, boundStartTextValue('.example-1'), 50, 'after mouseup bound start text');
88 | });
89 | });
90 |
91 | test('slide end handle interaction with rangeChanging action', function(assert) {
92 | visit('/');
93 | andThen(function() {
94 | expectWithin(assert, 70, endHandlePositionRounded('.example-1'), 1, 'before interaction end handle position');
95 | expectWithin(assert, 6000, boundEndTextValue('.example-1'), 1, 'before interaction bound end text');
96 | });
97 | rawMouseDown(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--end");
98 | andThen(function() {
99 | expectWithin(assert, 70, endHandlePositionRounded('.example-1'), 1, 'after mousedown end handle position');
100 | expectWithin(assert, 6000, boundEndTextValue('.example-1'), 50, 'after mousedown bound end text');
101 | });
102 | rawMouseMove(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--end", 4.5, 0);
103 | andThen(function() {
104 | expectWithin(assert, 74, endHandlePositionRounded('.example-1'), 1, 'after mousemove higher end handle position');
105 | expectWithin(assert, 6295, boundEndTextValue('.example-1'), 50, 'after mousemove higher bound end text');
106 | });
107 | rawMouseMove(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--end", -6, 0);
108 | andThen(function() {
109 | expectWithin(assert, 69, endHandlePositionRounded('.example-1'), 1, 'after mousemove lower end handle position');
110 | expectWithin(assert, 5878, boundEndTextValue('.example-1'), 50, 'after mousemove lower bound end text');
111 | });
112 | rawMouseMove(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--end", 0, 10);
113 | andThen(function() {
114 | expectWithin(assert, 69, endHandlePositionRounded('.example-1'), 1, 'after mousemove vertical end handle position');
115 | expectWithin(assert, 5878, boundEndTextValue('.example-1'), 50, 'after mousemove vertical bound end text');
116 | });
117 | rawMouseUp(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--end");
118 | andThen(function() {
119 | expectWithin(assert, 69, endHandlePositionRounded('.example-1'), 1, 'after mouseup end handle position');
120 | expectWithin(assert, 5878, boundEndTextValue('.example-1'), 50, 'after mouseup bound end text');
121 | });
122 | });
123 |
124 | test('check when two ranges in end positions', function(assert) {
125 | visit('/');
126 | andThen(function() {
127 | expectWithin(assert, 26, startHandlePositionRounded('.example-1'), 1, 'before interaction start handle position');
128 | expectWithin(assert, 70, endHandlePositionRounded('.example-1'), 1, 'before interaction end handle position');
129 | expectWithin(assert, 3000, boundStartTextValue('.example-1'), 1, 'before interaction bound start text');
130 | expectWithin(assert, 6000, boundEndTextValue('.example-1'), 1, 'before interaction bound end text');
131 | });
132 | rawMouseDown(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--end");
133 | rawMouseMove(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--end", 30, 0);
134 | andThen(function() {
135 | expectWithin(assert, 26, startHandlePositionRounded('.example-1'), 1, 'before interaction start handle position');
136 | expectWithin(assert, 100, endHandlePositionRounded('.example-1'), 1, 'after mousemove higher end handle position');
137 | expectWithin(assert, 3000, boundStartTextValue('.example-1'), 1, 'before interaction bound start text');
138 | expectWithin(assert, 8000, boundEndTextValue('.example-1'), 50, 'after mousemove higher bound end text');
139 | });
140 | rawMouseUp(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--end");
141 | rawMouseDown(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--start");
142 | rawMouseMove(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--start", 74, 0);
143 | andThen(function() {
144 | expectWithin(assert, 100, startHandlePositionRounded('.example-1'), 1, 'before interaction start handle position');
145 | expectWithin(assert, 100, endHandlePositionRounded('.example-1'), 1, 'after mousemove higher end handle position');
146 | expectWithin(assert, 8000, boundStartTextValue('.example-1'), 50, 'before interaction bound start text');
147 | expectWithin(assert, 8000, boundEndTextValue('.example-1'), 50, 'after mousemove higher bound end text');
148 | });
149 | rawMouseUp(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--start");
150 | rawMouseDown(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--start");
151 | rawMouseMove(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--start", -73.5, 0);
152 | andThen(function() {
153 | expectWithin(assert, 26, startHandlePositionRounded('.example-1'), 1, 'before interaction start handle position');
154 | expectWithin(assert, 100, endHandlePositionRounded('.example-1'), 1, 'after mousemove higher end handle position');
155 | expectWithin(assert, 3000, boundStartTextValue('.example-1'), 50, 'before interaction bound start text');
156 | expectWithin(assert, 8000, boundEndTextValue('.example-1'), 50, 'after mousemove higher bound end text');
157 | });
158 | });
159 |
160 | test('check when two ranges in start positions', function(assert) {
161 | visit('/');
162 | andThen(function() {
163 | expectWithin(assert, 27, startHandlePositionRounded('.example-1'), 1, 'before interaction start handle position');
164 | expectWithin(assert, 70, endHandlePositionRounded('.example-1'), 1, 'before interaction end handle position');
165 | expectWithin(assert, 3000, boundStartTextValue('.example-1'), 1, 'before interaction bound start text');
166 | expectWithin(assert, 6000, boundEndTextValue('.example-1'), 1, 'before interaction bound end text');
167 | });
168 | rawMouseDown(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--start");
169 | rawMouseMove(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--start", -27, 0);
170 | andThen(function() {
171 | expectWithin(assert, 0, startHandlePositionRounded('.example-1'), 1, 'before interaction start handle position');
172 | expectWithin(assert, 70, endHandlePositionRounded('.example-1'), 1, 'after mousemove higher end handle position');
173 | expectWithin(assert, 1200, boundStartTextValue('.example-1'), 1, 'before interaction bound start text');
174 | expectWithin(assert, 6000, boundEndTextValue('.example-1'), 50, 'after mousemove higher bound end text');
175 | });
176 | rawMouseUp(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--start");
177 | rawMouseDown(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--end");
178 | rawMouseMove(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--end", -71, 0);
179 | andThen(function() {
180 | expectWithin(assert, 0, startHandlePositionRounded('.example-1'), 1, 'before interaction start handle position');
181 | expectWithin(assert, 0, endHandlePositionRounded('.example-1'), 1, 'after mousemove higher end handle position');
182 | expectWithin(assert, 1200, boundStartTextValue('.example-1'), 1, 'before interaction bound start text');
183 | expectWithin(assert, 1200, boundEndTextValue('.example-1'), 50, 'after mousemove higher bound end text');
184 | });
185 | rawMouseUp(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--end");
186 | rawMouseDown(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--end");
187 | rawMouseMove(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-handle--end", 70.5, 0);
188 | andThen(function() {
189 | expectWithin(assert, 0, startHandlePositionRounded('.example-1'), 1, 'before interaction start handle position');
190 | expectWithin(assert, 70, endHandlePositionRounded('.example-1'), 1, 'after mousemove higher end handle position');
191 | expectWithin(assert, 1200, boundStartTextValue('.example-1'), 1, 'before interaction bound start text');
192 | expectWithin(assert, 6000, boundEndTextValue('.example-1'), 50, 'after mousemove higher bound end text');
193 | });
194 |
195 | });
196 |
197 | test('tap interaction', function(assert) {
198 | visit('/');
199 | andThen(function() {
200 | expectWithin(assert, 26, startHandlePositionRounded('.example-1'), 1, 'before interaction start handle position');
201 | expectWithin(assert, 3000, boundStartTextValue('.example-1'), 1, 'before interaction bound start text');
202 | expectWithin(assert, 70, endHandlePositionRounded('.example-1'), 1, 'before interaction end handle position');
203 | expectWithin(assert, 6000, boundEndTextValue('.example-1'), 1, 'before interaction bound end text');
204 | });
205 | rawTap(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-base", { x: -5, y: 0});
206 | andThen(function() {
207 | expectWithin(assert, 44, startHandlePositionRounded('.example-1'), 1, 'after lower tap start handle position');
208 | expectWithin(assert, 4250, boundStartTextValue('.example-1'), 50, 'after lower tap bound start text');
209 | expectWithin(assert, 70, endHandlePositionRounded('.example-1'), 1, 'after lower tap end handle position');
210 | expectWithin(assert, 6000, boundEndTextValue('.example-1'), 1, 'after lower tap bound end text');
211 | });
212 | rawTap(".example-1 .EmberRangeSlider", ".example-1 .EmberRangeSlider-base", { x: 23.5, y: 0});
213 | andThen(function() {
214 | expectWithin(assert, 44, startHandlePositionRounded('.example-1'), 1, 'after higher tap start handle position');
215 | expectWithin(assert, 4250, boundStartTextValue('.example-1'), 50, 'after higher tap bound start text');
216 | expectWithin(assert, 73, endHandlePositionRounded('.example-1'), 1, 'after higher tap end handle position');
217 | expectWithin(assert, 6185, boundEndTextValue('.example-1'), 50, 'after higher tap bound end text');
218 | });
219 | });
220 |
221 | test('slide start handle interaction with rangeChanged action', function(assert) {
222 | visit('/');
223 | andThen(function() {
224 | expectWithin(assert, 26, startHandlePositionRounded('.example-2'), 1, 'before interaction start handle position');
225 | expectWithin(assert, 3000, boundStartTextValue('.example-2'), 1, 'before interaction bound start text');
226 | expectIsSliding(assert, '.example-2', 'false', 'before interaction');
227 | });
228 | rawMouseDown(".example-2 .EmberRangeSlider", ".example-2 .EmberRangeSlider-handle--start");
229 | andThen(function() {
230 | expectWithin(assert, 26, startHandlePositionRounded('.example-2'), 1, 'after mousedown start handle position');
231 | expectWithin(assert, 3000, boundStartTextValue('.example-2'), 50, 'after mousedown bound start text');
232 | expectIsSliding(assert, '.example-2', 'true', 'after mousedown');
233 | });
234 | rawMouseMove(".example-2 .EmberRangeSlider", ".example-2 .EmberRangeSlider-handle--start", -10, 0);
235 | andThen(function() {
236 | expectWithin(assert, 16, startHandlePositionRounded('.example-2'), 1, 'after mousemove lower start handle position');
237 | expectWithin(assert, 3000, boundStartTextValue('.example-2'), 50, 'after mousemove lower bound start text');
238 | expectIsSliding(assert, '.example-2', 'true', 'after mousemove lower');
239 | });
240 | rawMouseMove(".example-2 .EmberRangeSlider", ".example-2 .EmberRangeSlider-handle--start", 13, 0);
241 | andThen(function() {
242 | expectWithin(assert, 28, startHandlePositionRounded('.example-2'), 1, 'after mousemove higher start handle position');
243 | expectWithin(assert, 3000, boundStartTextValue('.example-2'), 50, 'after mousemove higher bound start text');
244 | expectIsSliding(assert, '.example-2', 'true', 'after mousemove higher');
245 | });
246 | rawMouseUp(".example-2 .EmberRangeSlider", ".example-2 .EmberRangeSlider-handle--start");
247 | andThen(function() {
248 | expectWithin(assert, 28, startHandlePositionRounded('.example-2'), 1, 'after mouseup start handle position');
249 | expectWithin(assert, 3150, boundStartTextValue('.example-2'), 50, 'after mouseup bound start text');
250 | expectIsSliding(assert, '.example-2', 'false', 'after mouseup');
251 | });
252 | });
253 |
254 | test('slide end handle interaction with rangeChanged action', function(assert) {
255 | visit('/');
256 | andThen(function() {
257 | expectWithin(assert, 70, endHandlePositionRounded('.example-2'), 1, 'before interaction end handle position');
258 | expectWithin(assert, 6000, boundEndTextValue('.example-2'), 1, 'before interaction bound end text');
259 | expectIsSliding(assert, '.example-2', 'false', 'before interaction');
260 | });
261 | rawMouseDown(".example-2 .EmberRangeSlider", ".example-2 .EmberRangeSlider-handle--end");
262 | andThen(function() {
263 | expectWithin(assert, 70, endHandlePositionRounded('.example-2'), 1, 'after mousedown end handle position');
264 | expectWithin(assert, 6000, boundEndTextValue('.example-2'), 50, 'after mousedown bound end text');
265 | expectIsSliding(assert, '.example-2', 'true', 'after mousedown');
266 | });
267 | rawMouseMove(".example-2 .EmberRangeSlider", ".example-2 .EmberRangeSlider-handle--end", -10, 0);
268 | andThen(function() {
269 | expectWithin(assert, 60, endHandlePositionRounded('.example-2'), 1, 'after mousemove lower end handle position');
270 | expectWithin(assert, 6000, boundEndTextValue('.example-2'), 50, 'after mousemove lower bound end text');
271 | expectIsSliding(assert, '.example-2', 'true', 'after mousemove lower');
272 | });
273 | rawMouseMove(".example-2 .EmberRangeSlider", ".example-2 .EmberRangeSlider-handle--end", 13, 0);
274 | andThen(function() {
275 | expectWithin(assert, 73, endHandlePositionRounded('.example-2'), 1, 'after mousemove higher end handle position');
276 | expectWithin(assert, 6000, boundEndTextValue('.example-2'), 50, 'after mousemove higher bound end text');
277 | expectIsSliding(assert, '.example-2', 'true', 'after mousemove higher');
278 | });
279 | rawMouseUp(".example-2 .EmberRangeSlider", ".example-2 .EmberRangeSlider-handle--end");
280 | andThen(function() {
281 | expectWithin(assert, 73, endHandlePositionRounded('.example-2'), 1, 'after mouseup end handle position');
282 | expectWithin(assert, 6150, boundEndTextValue('.example-2'), 50, 'after mouseup bound end text');
283 | expectIsSliding(assert, '.example-2', 'false', 'after mouseup');
284 | });
285 | });
286 |
287 |
288 | test('slide start handle interaction with roundingHandler action', function(assert) {
289 | visit('/');
290 | andThen(function() {
291 | expectWithin(assert, 22, startHandlePositionRounded('.example-6'), 1, 'before interaction start handle position');
292 | expectWithin(assert, 3000, boundStartTextValue('.example-6'), 1, 'before interaction bound start text');
293 | });
294 | rawMouseDown(".example-6 .EmberRangeSlider", ".example-6 .EmberRangeSlider-handle--start");
295 | andThen(function() {
296 | expectWithin(assert, 22, startHandlePositionRounded('.example-6'), 1, 'after mousedown start handle position');
297 | expectWithin(assert, 3000, boundStartTextValue('.example-6'), 1, 'after mousedown bound start text');
298 | });
299 | rawMouseMove(".example-6 .EmberRangeSlider", ".example-6 .EmberRangeSlider-handle--start", 10, 0);
300 | andThen(function() {
301 | expectWithin(assert, 32, startHandlePositionRounded('.example-6'), 1, 'after mousemove lower start handle position');
302 | expectWithin(assert, 3000, boundStartTextValue('.example-6'), 1, 'after mousemove lower bound start text');
303 | });
304 | rawMouseMove(".example-6 .EmberRangeSlider", ".example-6 .EmberRangeSlider-handle--start", 10, 0);
305 | andThen(function() {
306 | expectWithin(assert, 42, startHandlePositionRounded('.example-6'), 1, 'after mousemove lower start handle position');
307 | expectWithin(assert, 4000, boundStartTextValue('.example-6'), 1, 'after mousemove lower bound start text');
308 | });
309 | rawMouseUp(".example-6 .EmberRangeSlider", ".example-6 .EmberRangeSlider-handle--start");
310 | andThen(function() {
311 | expectWithin(assert, 42, startHandlePositionRounded('.example-6'), 1, 'after mouseup start handle position');
312 | expectWithin(assert, 4000, boundStartTextValue('.example-6'), 1, 'after mouseup bound start text');
313 | });
314 | });
315 |
316 | test('slide end handle interaction with roundingHandler action', function(assert) {
317 | visit('/');
318 | andThen(function() {
319 | expectWithin(assert, 56, endHandlePositionRounded('.example-6'), 1, 'before interaction end handle position');
320 | expectWithin(assert, 6000, boundEndTextValue('.example-6'), 1, 'before interaction bound end text');
321 | });
322 | rawMouseDown(".example-6 .EmberRangeSlider", ".example-6 .EmberRangeSlider-handle--end");
323 | andThen(function() {
324 | expectWithin(assert, 56, endHandlePositionRounded('.example-6'), 1, 'after mousedown end handle position');
325 | expectWithin(assert, 6000, boundEndTextValue('.example-6'), 1, 'after mousedown bound end text');
326 | });
327 | rawMouseMove(".example-6 .EmberRangeSlider", ".example-6 .EmberRangeSlider-handle--end", 4, 0);
328 | andThen(function() {
329 | expectWithin(assert, 60, endHandlePositionRounded('.example-6'), 1, 'after mousemove lower end handle position');
330 | expectWithin(assert, 6000, boundEndTextValue('.example-6'), 1, 'after mousemove lower bound end text');
331 | });
332 | rawMouseMove(".example-6 .EmberRangeSlider", ".example-6 .EmberRangeSlider-handle--end", 15, 0);
333 | andThen(function() {
334 | expectWithin(assert, 75, endHandlePositionRounded('.example-6'), 1, 'after mousemove higher end handle position');
335 | expectWithin(assert, 7000, boundEndTextValue('.example-6'), 1, 'after mousemove higher bound end text');
336 | });
337 | rawMouseUp(".example-6 .EmberRangeSlider", ".example-6 .EmberRangeSlider-handle--end");
338 | andThen(function() {
339 | expectWithin(assert, 75, endHandlePositionRounded('.example-6'), 1, 'after mouseup end handle position');
340 | expectWithin(assert, 7000, boundEndTextValue('.example-6'), 1, 'after mouseup bound end text');
341 | });
342 | });
343 |
344 |
345 | test('custom components for handles', function(assert) {
346 | visit('/');
347 | andThen(function() {
348 | assert.equal(Ember.$('.example-3 .EmberRangeSlider-handle--start').css('background-color'), 'rgb(0, 128, 0)', 'start handle is green');
349 | assert.equal(Ember.$('.example-3 .EmberRangeSlider-handle--end').css('background-color'), 'rgb(0, 0, 255)', 'end handle is blue');
350 | });
351 | });
352 |
353 | test('bound to text fields', function(assert) {
354 | visit('/');
355 | andThen(function() {
356 | expectWithin(assert, 26, startHandlePositionRounded('.example-4'), 1, 'before interaction start handle position');
357 | expectWithin(assert, 3000, boundStartTextValue('.example-4'), 1, 'before interaction bound start text');
358 | expectWithin(assert, 3000, boundStartInputValue('.example-4'), 1, 'before interaction bound start input');
359 | expectWithin(assert, 70, endHandlePositionRounded('.example-4'), 1, 'before interaction end handle position');
360 | expectWithin(assert, 6000, boundEndTextValue('.example-4'), 1, 'before interaction bound end text');
361 | expectWithin(assert, 6000, boundEndInputValue('.example-4'), 1, 'before interaction bound end input');
362 | });
363 | fillIn('.example-4 input:eq(0)', '3200');
364 | andThen(function() {
365 | expectWithin(assert, 29, startHandlePositionRounded('.example-4'), 1, 'after update start text field, start handle position');
366 | expectWithin(assert, 3200, boundStartTextValue('.example-4'), 1, 'after update start text field, bound start text');
367 | expectWithin(assert, 3200, boundStartInputValue('.example-4'), 1, 'after update start text field, bound start input');
368 | expectWithin(assert, 70, endHandlePositionRounded('.example-4'), 1, 'after update start text field, end handle position');
369 | expectWithin(assert, 6000, boundEndTextValue('.example-4'), 1, 'after update start text field, bound end text');
370 | expectWithin(assert, 6000, boundEndInputValue('.example-4'), 1, 'after update start text field, bound end input');
371 | });
372 | fillIn('.example-4 input:eq(1)', '5000');
373 | andThen(function() {
374 | expectWithin(assert, 29, startHandlePositionRounded('.example-4'), 1, 'after update end text field, start handle position');
375 | expectWithin(assert, 3200, boundStartTextValue('.example-4'), 1, 'after update end text field, bound start text');
376 | expectWithin(assert, 3200, boundStartInputValue('.example-4'), 1, 'after update end text field, bound start input');
377 | expectWithin(assert, 55, endHandlePositionRounded('.example-4'), 1, 'after update end text field, end handle position');
378 | expectWithin(assert, 5000, boundEndTextValue('.example-4'), 1, 'after update end text field, bound end text');
379 | expectWithin(assert, 5000, boundEndInputValue('.example-4'), 1, 'after update end text field, bound end input');
380 | });
381 | rawPan(".example-4 .EmberRangeSlider", '.example-4 .EmberRangeSlider-handle--start', -6, 0);
382 | andThen(function() {
383 | expectWithin(assert, 23, startHandlePositionRounded('.example-4'), 1, 'after slide start handle, start handle position');
384 | expectWithin(assert, 2796, boundStartTextValue('.example-4'), 50, 'after slide start handle, bound start text');
385 | expectWithin(assert, 2796, boundStartInputValue('.example-4'), 50, 'after slide start handle, bound start input');
386 | expectWithin(assert, 55, endHandlePositionRounded('.example-4'), 1, 'after slide start handle, end handle position');
387 | expectWithin(assert, 5000, boundEndTextValue('.example-4'), 1, 'after slide start handle, bound end text');
388 | expectWithin(assert, 5000, boundEndInputValue('.example-4'), 50, 'after slide start handle, bound end input');
389 | });
390 | rawPan(".example-4 .EmberRangeSlider", '.example-4 .EmberRangeSlider-handle--end', -5.5, 0);
391 | andThen(function() {
392 | expectWithin(assert, 23, startHandlePositionRounded('.example-4'), 1, 'after slide end handle, start handle position');
393 | expectWithin(assert, 2796, boundStartTextValue('.example-4'), 50, 'after slide end handle, bound start text');
394 | expectWithin(assert, 2796, boundStartInputValue('.example-4'), 50, 'after slide end handle, bound start input');
395 | expectWithin(assert, 50, endHandlePositionRounded('.example-4'), 1, 'after slide end handle, end handle position');
396 | expectWithin(assert, 4600, boundEndTextValue('.example-4'), 50, 'after slide end handle, bound end text');
397 | expectWithin(assert, 4600, boundEndInputValue('.example-4'), 50, 'after slide end handle, bound end input');
398 | });
399 | });
400 |
--------------------------------------------------------------------------------