diff --git a/.gitignore b/.gitignore index 5148e52..d9eeb35 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,9 @@ coverage # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release +move-into-view.js +move-into-view.min.js + # Dependency directories node_modules jspm_packages diff --git a/dist.js b/dist.js new file mode 100644 index 0000000..b9d4936 --- /dev/null +++ b/dist.js @@ -0,0 +1 @@ +window.MoveIntoView = require('./'); diff --git a/index.js b/index.js index d252e1c..692d51f 100644 --- a/index.js +++ b/index.js @@ -50,16 +50,79 @@ function _parentsOf (target, isParent) { } } +/** + * Calculate wrapper's position based on an aspect ratio provided + * + * @name _position + * @function + * @access private + * @param {Number} aspectX aspect x (default 0.5) + * @param {Number} aspectY aspect y (default 0.5) + * @returns {Object} Coordinates + */ +function _position (aspectX, aspectY) { + aspectX = (typeof aspectX === 'undefined') ? 0.5 : aspectX; + aspectY = (typeof aspectY === 'undefined') ? 0.5 : aspectY; + + var parent = this.parent.getBoundingClientRect(); + var wrapper = this.wrapper.getBoundingClientRect(); + var target = this.target.getBoundingClientRect(); + + var x = target.left + (target.width * aspectX) - (parent.width * aspectX); + var y = target.top + (target.height * aspectY) - parent.height * aspectY; + + if (x < 0) x = 0; + if ((wrapper.width - x) < parent.width) x = wrapper.width - parent.width; + + if (y < 0) y = 0; + if ((wrapper.height - y) < parent.height) y = wrapper.height - parent.height; + + return { + x: x, + y: y + }; +} + +/** + * Applying elements position + * + * @name _move + * @function + * @access private + * @param {String} dir Direction + * @param {Number} aspectX + * @param {Number} aspectY + */ +function _move (dir, aspect) { + var position = this.position(aspect, aspect); + + if (dir === 'x' || dir === 'both') { + this.wrapper.style.left = (position.x * -1) + 'px'; + } + + if (dir === 'y' || dir === 'both') { + this.wrapper.style.top = (position.y * -1) + 'px'; + } + + return this; +} + function MoveIntoView (target, options) { + target = target || this; options = options || {}; - var parents = _parentsOf(target, options.isParent); - var view = { + target: target, wrapper: parents.wrapper, - parent: parents.parent + parent: parents.parent, + position: _position, + move: { } }; + view.move.x = _move.bind(view, 'x'); + view.move.y = _move.bind(view, 'y'); + view.move.both = _move.bind(view, 'both'); + return view; } diff --git a/package.json b/package.json index 6e7c915..80d4871 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "test": "npm run lint && npm run karma && codecov", "karma": "karma start karma.conf.js", "lint": "eslint .", - "semantic-release": "semantic-release pre && npm publish && semantic-release post" + "semantic-release": "semantic-release pre && npm publish && semantic-release post", + "prepublish": "mkdir -p dist && browserify --ignore-missing dist.js > move-into-view.js && browserify --ignore-missing dist.js | uglifyjs -c > move-into-view.min.js" }, "repository": { "type": "git", @@ -43,7 +44,8 @@ "karma-nyan-reporter": "^0.2.5", "karma-phantomjs-launcher": "^1.0.2", "mocha": "^3.2.0", - "watchify": "^3.8.0", - "semantic-release": "^6.3.2" + "semantic-release": "^6.3.2", + "uglify-js": "^2.8.16", + "watchify": "^3.8.0" } } diff --git a/tests/horizontal-positioning.test.js b/tests/horizontal-positioning.test.js new file mode 100644 index 0000000..785c70e --- /dev/null +++ b/tests/horizontal-positioning.test.js @@ -0,0 +1,122 @@ +var MoveIntoView = require('../'); +var expect = require('chai').expect; +var resetCSS = require('./css/reset'); + +var CSSTate = require('csstate'); +var cst = new CSSTate(); + +function vMock () { + var vParent = document.createElement('div'); + vParent.classList.add('scrollable'); + vParent.setAttribute('id', 'scrollable'); + var vChild = document.createElement('div'); + vChild.classList.add('wrapper'); + vChild.setAttribute('id', 'wrapper'); + vParent.appendChild(vChild); + + for (var i = 0; i < 10; i++) { + var el = document.createElement('div'); + el.classList.add('child'); + vChild.appendChild(el); + } + + return vParent; +} + +var vMockCSS = { + '.scrollable': { + 'position': 'relative', + 'width': '90px', + 'height': '120px', + 'overflow': 'hidden' + }, + + '.wrapper': { + 'position': 'absolute' + }, + + '.child': { + 'display': 'block', + 'width': '90px', + 'height': '60px' + } +}; + +describe('Test element setup', function () { + var vParent = null; + + beforeEach(function () { + cst.rule(resetCSS); + cst.rule(vMockCSS); + }); + + afterEach(function () { + cst.exit(); + }); + + before(function () { + vParent = vMock(); + document.body.appendChild(vParent); + }); + + after(function () { + document.body.removeChild(vParent); + }); + + it('should setup mockup currectly', function () { + expect(MoveIntoView).to.be.exists; + + var scrollable = document.getElementById('scrollable'); + + expect(scrollable.getBoundingClientRect().height).to.be.equal(120); + expect(scrollable.getBoundingClientRect().width).to.be.equal(90); + + var wrapper = document.getElementById('wrapper'); + + expect(wrapper.getBoundingClientRect().width).to.be.equal(90); + expect(wrapper.childNodes[1].getBoundingClientRect().left).to.be.equal(0); + expect(wrapper.childNodes[1].getBoundingClientRect().top).to.be.equal(60); + expect(wrapper.childNodes[1].getBoundingClientRect().height).to.be.equal(60); + }); + + it('should find out parent node', function () { + var wrapper = document.getElementById('wrapper'); + var firstChild = wrapper.childNodes[0]; + + expect(MoveIntoView(firstChild).wrapper).to.be.equal(wrapper); + expect(MoveIntoView(firstChild).parent).to.be.equal(vParent); + }); + + it('should handle negative y aspect ratios', function () { + var wrapper = document.getElementById('wrapper'); + var view = MoveIntoView(wrapper.childNodes[0]); + + expect(view.position(0, 0.5).y).to.be.equal(0); + expect(view.position(0, 0).y).to.be.equal(0); + expect(view.position(0, 1).y).to.be.equal(0); + }); + + it('should handle y aspect ratios', function () { + var wrapper = document.getElementById('wrapper'); + var view = MoveIntoView(wrapper.childNodes[1]); + + expect(view.position(0, 0).y).to.be.equal(60); + expect(view.position(0, 0.5).y).to.be.equal(30); + expect(view.position(0, 1).y).to.be.equal(0); + }); + + it('should handle over width x aspect ratios', function () { + var wrapper = document.getElementById('wrapper'); + var view = MoveIntoView(wrapper.childNodes[8]); + + expect(view.position(0, 0).y).to.be.equal(480); + expect(view.position(0, 0.5).y).to.be.equal(450); + expect(view.position(0, 1).y).to.be.equal(420); + + view = MoveIntoView(wrapper.childNodes[9]); + expect(view.position(0, 0).y).to.be.equal(480); + expect(view.position(0, 0.5).y).to.be.equal(480); + expect(view.position(0, 1).y).to.be.equal(480); + }); +}); + diff --git a/tests/moving-position.test.js b/tests/moving-position.test.js new file mode 100644 index 0000000..97edd6f --- /dev/null +++ b/tests/moving-position.test.js @@ -0,0 +1,77 @@ +var MoveIntoView = require('../'); +var expect = require('chai').expect; +var resetCSS = require('./css/reset'); + +var CSSTate = require('csstate'); +var cst = new CSSTate(); + +function vMock () { + var vParent = document.createElement('div'); + vParent.classList.add('scrollable'); + vParent.setAttribute('id', 'scrollable'); + var vChild = document.createElement('div'); + vChild.classList.add('wrapper'); + vChild.setAttribute('id', 'wrapper'); + vParent.appendChild(vChild); + + for (var i = 0; i < 10; i++) { + var el = document.createElement('div'); + el.classList.add('child'); + vChild.appendChild(el); + } + + return vParent; +} + +var vMockCSS = { + '.scrollable': { + 'position': 'relative', + 'width': '90px', + 'height': '120px', + 'overflow': 'hidden' + }, + + '.wrapper': { + 'position': 'absolute' + }, + + '.child': { + 'display': 'block', + 'width': '90px', + 'height': '60px' + } +}; + +describe('Test element setup', function () { + var vParent = null; + + beforeEach(function () { + cst.rule(resetCSS); + cst.rule(vMockCSS); + }); + + afterEach(function () { + cst.exit(); + }); + + before(function () { + vParent = vMock(); + document.body.appendChild(vParent); + }); + + after(function () { + document.body.removeChild(vParent); + }); + + it('should move child', function () { + var wrapper = document.getElementById('wrapper'); + var child = wrapper.childNodes[1]; + MoveIntoView(child).move.y(0.5); + expect(wrapper.style.top).to.be.equal('-30px'); + MoveIntoView(child).move.y(1); + expect(wrapper.style.top).to.be.equal('0px'); + MoveIntoView(child).move.y(0); + expect(wrapper.style.top).to.be.equal('-60px'); + }); +}); + diff --git a/tests/element-setup.test.js b/tests/vertical-positioning.test.js similarity index 60% rename from tests/element-setup.test.js rename to tests/vertical-positioning.test.js index 7b49eb9..eb48026 100644 --- a/tests/element-setup.test.js +++ b/tests/vertical-positioning.test.js @@ -38,9 +38,8 @@ var vMockCSS = { '.child': { 'display': 'inline-block', - 'width': '90px', - 'height': '86px', - 'margin': '0px 2px' + 'width': '60px', + 'height': '86px' } }; @@ -75,8 +74,8 @@ describe('Test element setup', function () { var wrapper = document.getElementById('wrapper'); - expect(wrapper.getBoundingClientRect().width).to.be.equal(940); - expect(wrapper.childNodes[1].getBoundingClientRect().left).to.be.equal(96); + expect(wrapper.getBoundingClientRect().width).to.be.equal(600); + expect(wrapper.childNodes[1].getBoundingClientRect().left).to.be.equal(60); }); it('should find out parent node', function () { @@ -86,5 +85,37 @@ describe('Test element setup', function () { expect(MoveIntoView(firstChild).wrapper).to.be.equal(wrapper); expect(MoveIntoView(firstChild).parent).to.be.equal(vParent); }); + + it('should handle negative x aspect ratios', function () { + var wrapper = document.getElementById('wrapper'); + var view = MoveIntoView(wrapper.childNodes[0]); + + expect(view.position(0.5).x).to.be.equal(0); + expect(view.position(0).x).to.be.equal(0); + expect(view.position(1).x).to.be.equal(0); + }); + + it('should handle x aspect ratios', function () { + var wrapper = document.getElementById('wrapper'); + var view = MoveIntoView(wrapper.childNodes[1]); + + expect(view.position(0).x).to.be.equal(60); + expect(view.position(0.5).x).to.be.equal(30); + expect(view.position(1).x).to.be.equal(0); + }); + + it('should handle over width x aspect ratios', function () { + var wrapper = document.getElementById('wrapper'); + var view = MoveIntoView(wrapper.childNodes[8]); + + expect(view.position(0).x).to.be.equal(480); + expect(view.position(0.5).x).to.be.equal(450); + expect(view.position(1).x).to.be.equal(420); + + view = MoveIntoView(wrapper.childNodes[9]); + expect(view.position(0).x).to.be.equal(480); + expect(view.position(0.5).x).to.be.equal(480); + expect(view.position(1).x).to.be.equal(480); + }); });