diff --git a/README.md b/README.md index 2023e02..20d916c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Rangeable [![npm version](https://badge.fury.io/js/rangeable.svg)](https://badge.fury.io/js/rangeable) [![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/Mobius1/Rangeable/blob/master/LICENSE) [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/Mobius1/Rangeable.svg)](http://isitmaintained.com/project/Mobius1/Rangeable "Average time to resolve an issue") [![Percentage of issues still open](http://isitmaintained.com/badge/open/Mobius1/Rangeable.svg)](http://isitmaintained.com/project/Mobius1/Rangeable "Percentage of issues still open") ![](http://img.badgesize.io/Mobius1/Rangeable/master/dist/rangeable.min.js) ![](http://img.badgesize.io/Mobius1/Rangeable/master/dist/rangeable.min.js?compression=gzip&label=gzipped) -A small dependency-free lib to transform `` elements into something prettier and more configurable. +A dependency-free, responsive and touch-enabled javascript range slider to make `` elements prettier and more configurable. - [x] No dependencies - [x] <3kb gzipped diff --git a/dist/rangeable.min.css b/dist/rangeable.min.css index 322678f..22a20f2 100644 --- a/dist/rangeable.min.css +++ b/dist/rangeable.min.css @@ -4,7 +4,7 @@ Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. - Version: 0.0.10 + Version: 0.0.11 */ body{margin:0;width:100vw;height:100vh;display:flex;align-items:center;justify-content:center;flex-flow:column}.container{width:20vw}.rangeable-container{cursor:pointer;width:100%}.rangeable-container.rangeable-disabled{opacity:.6;cursor:not-allowed}.rangeable-container.rangeable-multiple.combined-tooltip .rangeable-handle .rangeable-tooltip,.rangeable-container.rangeable-vertical.combined-tooltip .rangeable-handle .rangeable-tooltip{opacity:0}.rangeable-container.focus .rangeable-handle{border:1px solid #74b9ff}.rangeable-container .rangeable-track{width:100%;height:8px;background-color:#ccc;position:relative;border-radius:4px}.rangeable-container .rangeable-progress{height:8px;width:100%;background-color:#3db13d;position:absolute;left:0;top:0;border-radius:4px;-webkit-transform-origin:0 0 0;transform-origin:0 0 0}.rangeable-container .rangeable-progress>.rangeable-tooltip{display:none;z-index:11;top:auto;bottom:calc(100% + 7px + 10px);white-space:nowrap}.rangeable-container.rangeable-multiple .rangeable-handle:nth-child(1){left:-11px}.rangeable-container.rangeable-multiple .rangeable-handle:nth-child(2){right:-11px}.rangeable-container .rangeable-handle{box-sizing:border-box;width:22px;height:22px;border:6px solid #3db13d;border-radius:50%;background-color:#fff;position:absolute;top:-7px;right:-11px}.rangeable-container .rangeable-handle:focus{outline:0}.rangeable-container .rangeable-handle:focus::after{position:absolute;width:22px;height:22px;bottom:-6px;right:-6px;outline:#000 dotted 1px;content:""}.rangeable-container .rangeable-handle.active{z-index:10}.rangeable-container .rangeable-handle .rangeable-tooltip{display:none}.rangeable-container.combined-tooltip.dragging .rangeable-progress>.rangeable-tooltip,.rangeable-container.combined-tooltip.show-tooltip .rangeable-progress>.rangeable-tooltip,.rangeable-container.dragging.has-tooltip .rangeable-handle .rangeable-tooltip,.rangeable-container.rangeable-vertical.combined-tooltip .rangeable-progress>.rangeable-tooltip::before,.rangeable-container.show-tooltip.has-tooltip .rangeable-handle .rangeable-tooltip{display:block}.rangeable-container .rangeable-tooltip{position:absolute;bottom:calc(100% + 7px + 10px - 1px);right:50%;-webkit-transform:translate3d(50%,0,0);transform:translate3d(50%,0,0);text-align:center;padding:2px 13px;background-color:#3db13d;border-radius:3px;font-weight:700;color:#fff;font-family:Consolas,Courier New,Lucida Console,sans-serif}.rangeable-container .rangeable-tooltip::before{width:0;height:0;border-width:5px 5px 0;border-style:solid;border-color:#3db13d transparent transparent;position:absolute;left:50%;top:100%;-webkit-transform:translate3d(-50%,0,0);transform:translate3d(-50%,0,0);content:""}.rangeable-container.rangeable-vertical{height:100%;width:auto}.rangeable-container.rangeable-vertical .rangeable-track{width:8px;height:100%}.rangeable-container.rangeable-vertical .rangeable-progress{width:8px;height:100%;top:auto;bottom:0;-webkit-transform-origin:0 100% 0;transform-origin:0 100% 0}.rangeable-container.rangeable-vertical .rangeable-handle{right:auto;left:-7px;top:-11px}.rangeable-container.rangeable-vertical .rangeable-tooltip{position:absolute;top:50%;left:24px;right:auto;bottom:auto;-webkit-transform:translate3d(0,-50%,0);transform:translate3d(0,-50%,0)}.rangeable-container.rangeable-vertical .rangeable-tooltip::before{right:100%;left:auto;top:50%;-webkit-transform:translate3d(0,-50%,0);transform:translate3d(0,-50%,0);border-width:5px 5px 5px 0;border-color:transparent #3db13d transparent transparent}.rangeable-container.rangeable-vertical.rangeable-multiple .rangeable-handle:nth-child(1){top:-11px;left:-7px}.rangeable-container.rangeable-vertical.rangeable-multiple .rangeable-handle:nth-child(2){bottom:-11px;top:auto}.rangeable-input{position:absolute;overflow:hidden;clip:rect(0,0,0,0);width:1px;height:1px;margin:-1px;padding:0;border:0}.rangeable-input:focus+.rangeable-track .rangeable-handle::after{position:absolute;width:22px;height:22px;bottom:-6px;right:-6px;outline:#000 dotted 1px;content:""} \ No newline at end of file diff --git a/dist/rangeable.min.js b/dist/rangeable.min.js index c83888d..aff5f75 100644 --- a/dist/rangeable.min.js +++ b/dist/rangeable.min.js @@ -4,7 +4,7 @@ Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. - Version: 0.0.10 + Version: 0.0.11 */ -var Rangeable=function(h,i){"string"==typeof h&&(h=document.querySelector(h)),this.input=h,this.config=Object.assign({},{type:"single",tooltips:"always",classes:{input:"rangeable-input",container:"rangeable-container",vertical:"rangeable-vertical",progress:"rangeable-progress",handle:"rangeable-handle",tooltip:"rangeable-tooltip",track:"rangeable-track",multiple:"rangeable-multiple",disabled:"rangeable-disabled"}},i),this.mouseAxis={x:"clientX",y:"clientY"},this.trackSize={x:"width",y:"height"},this.trackPos={x:"left",y:"top"},this.lastPos=0,this.double="double"===this.config.type||Array.isArray(this.config.value),this.touch="ontouchstart"in window||window.DocumentTouch&&document instanceof DocumentTouch,this.init(),this.onInit()};Rangeable.prototype.init=function(){if(!this.input.rangeable){var i,h={min:0,max:100,step:1,value:this.input.value};for(i in h)this.input[i]||(this.input[i]=h[i]),void 0!==this.config[i]&&(this.input[i]=this.config[i]);this.axis=this.config.vertical?"y":"x",this.input.rangeable=this,this.double?(this.input.values=this.config.value?this.config.value:[this.input.min,this.input.max],this.input.defaultValues=this.input.values.slice()):this.input.defaultValue||(this.input.defaultValue=this.input.value),this.render(),this.initialised=!0}},Rangeable.prototype.render=function(){var h=this.config,i=h.classes,j=this.createElement("div",i.container),k=this.createElement("div",i.track),l=this.createElement("div",i.progress),m=this.createElement("div",i.handle),n=this.createElement("div",i.tooltip);k.appendChild(l),this.double?(m=[this.createElement("div",i.handle),this.createElement("div",i.handle)],n=[this.createElement("div",i.tooltip),this.createElement("div",i.tooltip),this.createElement("div",i.tooltip)],m.forEach(function(o,p){o.index=p,l.appendChild(o),o.appendChild(n[p]),o.tabIndex=1}),h.vertical&&l.appendChild(m[0]),l.appendChild(n[2]),j.classList.add(i.multiple)):(l.appendChild(m),m.appendChild(n)),this.nodes={container:j,track:k,progress:l,handle:m,tooltip:n},j.appendChild(k),h.vertical&&j.classList.add(i.vertical),h.size&&(j.style[this.trackSize[this.axis]]=isNaN(h.size)?h.size:h.size+"px"),h.tooltips&&(j.classList.add("has-tooltip"),"string"==typeof h.tooltips&&"always"===h.tooltips&&j.classList.add("show-tooltip")),this.input.parentNode.insertBefore(j,this.input),j.insertBefore(this.input,k),this.input.classList.add(i.input),this.bind(),this.update()},Rangeable.prototype.reset=function(){var h=this;this.double?this.input.defaultValues.forEach(function(i,j){h.setValue(i,j)}):this.setValue(this.input.defaultValue),this.onEnd()},Rangeable.prototype.setValueFromPosition=function(h){var i=parseFloat(this.input.min),j=parseFloat(this.input.max),k=parseFloat(this.input.step),l=this.touch?h.touches[0][this.mouseAxis[this.axis]]:h[this.mouseAxis[this.axis]],m=l-this.rects.container[this.trackPos[this.axis]],n=this.rects.container[this.trackSize[this.axis]];if("mousedown"===h.type&&(!this.double&&this.nodes.handle.contains(h.target)||this.double&&(this.nodes.handle[0].contains(h.target)||this.nodes.handle[1].contains(h.target))))return!1;if(h=Math.ceil(((this.config.vertical?100*((n-m)/n):100*(m/n))*(j-i)/100+i)/k)*k,l>=this.lastPos&&(h-=k),parseFloat(h)===parseFloat(this.startValue))return!1;if(k=!1,this.double)switch(k=this.activeHandle.index,k){case 0:h>=this.input.values[1]&&(h=this.input.values[1]);break;case 1:h<=this.input.values[0]&&(h=this.input.values[0]);}this.setValue(h,k)},Rangeable.prototype.change=function(){},Rangeable.prototype.touchstart=function(h){this.down(h)},Rangeable.prototype.down=function(h){h.preventDefault(),this.startValue=this.getValue(),this.onStart(),this.nodes.container.classList.add("dragging"),this.recalculate(),this.activeHandle=this.getHandle(h),this.activeHandle.classList.add("active"),this.setValueFromPosition(h),this.touch?(document.addEventListener("touchmove",this.events.move,!1),document.addEventListener("touchend",this.events.up,!1),document.addEventListener("touchcancel",this.events.up,!1)):(document.addEventListener("mousemove",this.events.move,!1),document.addEventListener("mouseup",this.events.up,!1))},Rangeable.prototype.move=function(h){this.setValueFromPosition(h),this.lastPos=this.touch?h.touches[0][this.mouseAxis[this.axis]]:h[this.mouseAxis[this.axis]]},Rangeable.prototype.up=function(){this.stopValue=this.getValue(),this.nodes.container.classList.remove("dragging"),this.onEnd(),this.activeHandle.classList.remove("active"),this.activeHandle=!1,document.removeEventListener("mousemove",this.events.move),document.removeEventListener("mouseup",this.events.up),document.removeEventListener("touchmove",this.events.move),document.removeEventListener("touchend",this.events.up),document.removeEventListener("touchcancel",this.events.up),this.startValue!==this.stopValue&&this.input.dispatchEvent(new Event("change")),this.startValue=null},Rangeable.prototype.recalculate=function(){var h=[];this.double?this.nodes.handle.forEach(function(i,j){h[j]=i.getBoundingClientRect()}):h=this.nodes.handle.getBoundingClientRect(),this.rects={handle:h,container:this.nodes.container.getBoundingClientRect()}},Rangeable.prototype.update=function(){var h=this;this.recalculate(),this.accuracy=0,this.input.step.includes(".")&&(this.accuracy=(this.input.step.split(".")[1]||[]).length),this.double?this.input.values.forEach(function(i,j){h.setValue(i,j)}):this.setValue()},Rangeable.prototype.getValue=function(){return this.double?this.input.values:this.input.value},Rangeable.prototype.parseValue=function(h){var i=parseFloat(this.input.min),j=parseFloat(this.input.max);return void 0===h&&(h=this.input.value),h=parseFloat(h),h=h.toFixed(this.accuracy),hj&&(h=j.toFixed(this.accuracy)),h},Rangeable.prototype.setValue=function(h,i){var j=this.nodes;if(h=this.parseValue(h),this.double&&void 0===i)return!1;var k=this.initialised&&(h!==this.input.value||this.nativeEvent);if(this.double){var l=this.input.values;if(l[i]=h,this.config.tooltips){j.tooltip[i].textContent=h;var m=this.tipsIntersecting();j.container.classList.toggle("combined-tooltip",m),m&&(j.tooltip[2].textContent=l[0]===l[1]?l[0]:l[0]+" - "+l[1])}}else this.input.value=h,j.tooltip.textContent=h;this.setPosition(h,i),k&&(this.onChange(),this.nativeEvent||this.input.dispatchEvent(new Event("input")),this.nativeEvent=!1)},Rangeable.prototype.native=function(){this.nativeEvent=!0,this.setValue()},Rangeable.prototype.setPosition=function(h){if(this.double){h=this.getPosition(this.input.values[0]);var i=this.getPosition(this.input.values[1]);this.nodes.progress.style[this.config.vertical?"bottom":"left"]=h+"px",h=i-h}else h=this.getPosition();this.nodes.progress.style[this.trackSize[this.axis]]=h+"px"},Rangeable.prototype.getPosition=function(h){h=void 0===h?this.input.value:h;var i=parseFloat(this.input.min),j=parseFloat(this.input.max);return(h-i)/(j-i)*this.rects.container[this.trackSize[this.axis]]},Rangeable.prototype.tipsIntersecting=function(){var h=this.nodes.tooltip,i=h[0].getBoundingClientRect();return h=h[1].getBoundingClientRect(),!(i.righth.right||i.bottomh.bottom)},Rangeable.prototype.getHandle=function(h){if(!this.double)return this.nodes.handle;var i=this.rects,j=Math.abs(h[this.mouseAxis[this.axis]]-i.handle[0][this.trackPos[this.axis]]);return i=Math.abs(h[this.mouseAxis[this.axis]]-i.handle[1][this.trackPos[this.axis]]),(h=h.target.closest("."+this.config.classes.handle))?h:j>i?this.nodes.handle[1]:this.nodes.handle[0]},Rangeable.prototype.destroy=function(){this.input.rangeable&&(this.unbind(),this.input.classList.remove(this.config.classes.input),this.nodes.container.parentNode.replaceChild(this.input,this.nodes.container),delete this.input.rangeable,this.initialised=!1)},Rangeable.prototype.onInit=function(){this.isFunction(this.config.onInit)&&this.config.onInit.call(this,this.getValue())},Rangeable.prototype.onStart=function(){this.isFunction(this.config.onStart)&&this.config.onStart.call(this,this.getValue())},Rangeable.prototype.onChange=function(){this.isFunction(this.config.onChange)&&this.config.onChange.call(this,this.getValue())},Rangeable.prototype.onEnd=function(){this.isFunction(this.config.onEnd)&&this.config.onEnd.call(this,this.getValue())},Rangeable.prototype.enable=function(){this.disabled&&(this.touch?this.nodes.container.addEventListener("touchstart",this.events.touchstart,!1):this.nodes.container.addEventListener("mousedown",this.events.down),this.nodes.container.classList.remove(this.config.classes.disabled),this.disabled=!1)},Rangeable.prototype.disable=function(){this.disabled||(this.touch?this.nodes.container.removeEventListener("touchstart",this.events.touchstart):this.nodes.container.removeEventListener("mousedown",this.events.down),this.nodes.container.classList.add(this.config.classes.disabled),this.disabled=!0)},Rangeable.prototype.bind=function(){this.events={down:this.down.bind(this),touchstart:this.touchstart.bind(this),move:this.move.bind(this),up:this.up.bind(this),update:this.update.bind(this),change:this.change.bind(this),reset:this.reset.bind(this),set:this.native.bind(this)},this.events.scroll=this.throttle(this.events.update,100),this.events.resize=this.throttle(this.events.update,50),document.addEventListener("scroll",this.events.scroll,!1),window.addEventListener("resize",this.events.resize,!1),this.input.addEventListener("change",this.events.change,!1),this.touch?this.nodes.container.addEventListener("touchstart",this.events.touchstart,!1):this.nodes.container.addEventListener("mousedown",this.events.down),this.input.addEventListener("input",this.events.set),this.input.form&&this.input.form.addEventListener("reset",this.events.reset,!1)},Rangeable.prototype.unbind=function(){document.removeEventListener("scroll",this.events.scroll),window.removeEventListener("resize",this.events.resize),this.input.removeEventListener("change",this.events.change),this.touch?this.nodes.container.removeEventListener("touchstart",this.events.touchstart):this.nodes.container.removeEventListener("mousedown",this.events.down),this.input.removeEventListener("input",this.events.set),this.input.form&&this.input.form.removeEventListener("reset",this.events.reset),this.events=null},Rangeable.prototype.createElement=function(h,i){var j=document.createElement(h);return i&&j.classList.add(i),j},Rangeable.prototype.isFunction=function(h){return h&&"function"==typeof h},Rangeable.prototype.throttle=function(h,i,j){var k;return function(){if(j=j||this,!k)return h.apply(j,arguments),k=!0,setTimeout(function(){k=!1},i)}}; \ No newline at end of file +var Rangeable=function(h,i){"string"==typeof h&&(h=document.querySelector(h)),this.input=h,this.config=Object.assign({},{type:"single",tooltips:"always",classes:{input:"rangeable-input",container:"rangeable-container",vertical:"rangeable-vertical",progress:"rangeable-progress",handle:"rangeable-handle",tooltip:"rangeable-tooltip",track:"rangeable-track",multiple:"rangeable-multiple",disabled:"rangeable-disabled"}},i),this.mouseAxis={x:"clientX",y:"clientY"},this.trackSize={x:"width",y:"height"},this.trackPos={x:"left",y:"top"},this.lastPos=0,this.double="double"===this.config.type||Array.isArray(this.config.value),this.touch="ontouchstart"in window||window.DocumentTouch&&document instanceof DocumentTouch,this.init(),this.onInit()};Rangeable.prototype.init=function(){if(!this.input.rangeable){var i,h={min:0,max:100,step:1,value:this.input.value};for(i in h)this.input[i]||(this.input[i]=h[i]),void 0!==this.config[i]&&(this.input[i]=this.config[i]);this.axis=this.config.vertical?"y":"x",this.input.rangeable=this,this.double?(this.input.values=this.config.value?this.config.value:[this.input.min,this.input.max],this.input.defaultValues=this.input.values.slice()):this.input.defaultValue||(this.input.defaultValue=this.input.value),this.render(),this.initialised=!0}},Rangeable.prototype.render=function(){var h=this.config,i=h.classes,j=this.createElement("div",i.container),k=this.createElement("div",i.track),l=this.createElement("div",i.progress),m=this.createElement("div",i.handle),n=this.createElement("div",i.tooltip);k.appendChild(l),this.double?(m=[this.createElement("div",i.handle),this.createElement("div",i.handle)],n=[this.createElement("div",i.tooltip),this.createElement("div",i.tooltip),this.createElement("div",i.tooltip)],m.forEach(function(o,p){o.index=p,l.appendChild(o),o.appendChild(n[p]),o.tabIndex=1}),this.input.tabIndex=-1,h.vertical&&l.appendChild(m[0]),l.appendChild(n[2]),j.classList.add(i.multiple)):(l.appendChild(m),m.appendChild(n)),this.nodes={container:j,track:k,progress:l,handle:m,tooltip:n},j.appendChild(k),h.vertical&&j.classList.add(i.vertical),h.size&&(j.style[this.trackSize[this.axis]]=isNaN(h.size)?h.size:h.size+"px"),h.tooltips&&(j.classList.add("has-tooltip"),"string"==typeof h.tooltips&&"always"===h.tooltips&&j.classList.add("show-tooltip")),this.input.parentNode.insertBefore(j,this.input),j.insertBefore(this.input,k),this.input.classList.add(i.input),this.bind(),this.update()},Rangeable.prototype.reset=function(){this.double?this.input.defaultValues.forEach(this.setValue,this):this.setValue(this.input.defaultValue),this.onEnd()},Rangeable.prototype.setValueFromPosition=function(h){var i=parseFloat(this.input.min),j=parseFloat(this.input.max),k=parseFloat(this.input.step),l=this.touch?h.touches[0][this.mouseAxis[this.axis]]:h[this.mouseAxis[this.axis]],m=l-this.rects.container[this.trackPos[this.axis]],n=this.rects.container[this.trackSize[this.axis]];return"mousedown"===h.type&&(!this.double&&this.nodes.handle.contains(h.target)||this.double&&(this.nodes.handle[0].contains(h.target)||this.nodes.handle[1].contains(h.target)))?!1:(h=Math.ceil(((this.config.vertical?100*((n-m)/n):100*(m/n))*(j-i)/100+i)/k)*k,l>=this.lastPos&&(h-=k),parseFloat(h)!==parseFloat(this.startValue)&&void(k=!1,this.double&&(k=this.activeHandle.index,!k&&h>=this.input.values[1]?h=this.input.values[1]:k&&h<=this.input.values[0]&&(h=this.input.values[0])),this.setValue(h,k)))},Rangeable.prototype.start=function(h){h.preventDefault(),this.startValue=this.getValue(),this.onStart(),this.nodes.container.classList.add("dragging"),this.recalculate(),this.activeHandle=this.getHandle(h),this.activeHandle.classList.add("active"),this.setValueFromPosition(h),this.touch?(document.addEventListener("touchmove",this.events.move,!1),document.addEventListener("touchend",this.events.stop,!1),document.addEventListener("touchcancel",this.events.stop,!1)):(document.addEventListener("mousemove",this.events.move,!1),document.addEventListener("mouseup",this.events.stop,!1))},Rangeable.prototype.move=function(h){this.setValueFromPosition(h),this.lastPos=this.touch?h.touches[0][this.mouseAxis[this.axis]]:h[this.mouseAxis[this.axis]]},Rangeable.prototype.stop=function(){this.stopValue=this.getValue(),this.nodes.container.classList.remove("dragging"),this.onEnd(),this.activeHandle.classList.remove("active"),this.activeHandle=!1,document.removeEventListener("mousemove",this.events.move),document.removeEventListener("mouseup",this.events.stop),document.removeEventListener("touchmove",this.events.move),document.removeEventListener("touchend",this.events.stop),document.removeEventListener("touchcancel",this.events.stop),this.startValue!==this.stopValue&&this.input.dispatchEvent(new Event("change")),this.startValue=null},Rangeable.prototype.keydown=function(h){var i=this;this.double&&this.nodes.handle.forEach(function(j){if(j===document.activeElement)switch(h.key){case"ArrowRight":case"ArrowUp":i.stepUp(j.index);break;case"ArrowLeft":case"ArrowDown":i.stepDown(j.index);}})},Rangeable.prototype.stepUp=function(h){var i=parseFloat(this.input.step),j=this.getValue();this.double&&void 0!==h&&(j=j[h]),i=this.limit(parseFloat(j)+i,h),this.setValue(i,h)},Rangeable.prototype.stepDown=function(h){var i=parseFloat(this.input.step),j=this.getValue();this.double&&void 0!==h&&(j=j[h]),i=this.limit(parseFloat(j)-i,h),this.setValue(i,h)},Rangeable.prototype.limit=function(h,i){var j=this.input,k=parseFloat(j.min),l=parseFloat(j.max);return this.getValue(),this.double&&void 0!==i&&(!i&&h>=j.values[1]?h=j.values[1]:i&&h<=j.values[0]&&(h=j.values[0])),h>=l?h=l:h<=k&&(h=k),h},Rangeable.prototype.recalculate=function(){var h=[];this.double?this.nodes.handle.forEach(function(i,j){h[j]=i.getBoundingClientRect()}):h=this.nodes.handle.getBoundingClientRect(),this.rects={handle:h,container:this.nodes.container.getBoundingClientRect()}},Rangeable.prototype.update=function(){var h=this;this.recalculate(),this.accuracy=0,this.input.step.includes(".")&&(this.accuracy=(this.input.step.split(".")[1]||[]).length),this.double?this.input.values.forEach(function(i,j){h.setValue(i,j)}):this.setValue()},Rangeable.prototype.getValue=function(){return this.double?this.input.values:this.input.value},Rangeable.prototype.parseValue=function(h){var i=parseFloat(this.input.min),j=parseFloat(this.input.max);return void 0===h&&(h=this.input.value),h=parseFloat(h),h=h.toFixed(this.accuracy),hj&&(h=j.toFixed(this.accuracy)),h},Rangeable.prototype.setValue=function(h,i){var j=this.nodes;if(h=this.parseValue(h),this.double&&void 0===i)return!1;var k=this.initialised&&(h!==this.input.value||this.nativeEvent);if(this.double){var l=this.input.values;if(l[i]=h,this.config.tooltips){j.tooltip[i].textContent=h;var m=j.tooltip[0].getBoundingClientRect(),n=j.tooltip[1].getBoundingClientRect();m=!(m.rightn.right||m.bottomn.bottom),j.container.classList.toggle("combined-tooltip",m),m&&(j.tooltip[2].textContent=l[0]===l[1]?l[0]:l[0]+" - "+l[1])}}else this.input.value=h,j.tooltip.textContent=h;this.setPosition(h,i),k&&(this.onChange(),this.nativeEvent||this.input.dispatchEvent(new Event("input")),this.nativeEvent=!1)},Rangeable.prototype.native=function(){this.nativeEvent=!0,this.setValue()},Rangeable.prototype.setPosition=function(h){if(this.double){h=this.getPosition(this.input.values[0]);var i=this.getPosition(this.input.values[1]);this.nodes.progress.style[this.config.vertical?"bottom":"left"]=h+"px",h=i-h}else h=this.getPosition();this.nodes.progress.style[this.trackSize[this.axis]]=h+"px"},Rangeable.prototype.getPosition=function(h){h=void 0===h?this.input.value:h;var i=parseFloat(this.input.min),j=parseFloat(this.input.max);return(h-i)/(j-i)*this.rects.container[this.trackSize[this.axis]]},Rangeable.prototype.getHandle=function(h){if(!this.double)return this.nodes.handle;var i=this.rects,j=Math.abs(h[this.mouseAxis[this.axis]]-i.handle[0][this.trackPos[this.axis]]);return i=Math.abs(h[this.mouseAxis[this.axis]]-i.handle[1][this.trackPos[this.axis]]),(h=h.target.closest("."+this.config.classes.handle))?h:j>i?this.nodes.handle[1]:this.nodes.handle[0]},Rangeable.prototype.onInit=function(){this.isFunction(this.config.onInit)&&this.config.onInit.call(this,this.getValue())},Rangeable.prototype.onStart=function(){this.isFunction(this.config.onStart)&&this.config.onStart.call(this,this.getValue())},Rangeable.prototype.onChange=function(){this.isFunction(this.config.onChange)&&this.config.onChange.call(this,this.getValue())},Rangeable.prototype.onEnd=function(){this.isFunction(this.config.onEnd)&&this.config.onEnd.call(this,this.getValue())},Rangeable.prototype.enable=function(){this.disabled&&(this.touch?this.nodes.container.addEventListener("touchstart",this.events.touchstart,!1):this.nodes.container.addEventListener("mousedown",this.events.down),this.nodes.container.classList.remove(this.config.classes.disabled),this.disabled=!1)},Rangeable.prototype.disable=function(){this.disabled||(this.touch?this.nodes.container.removeEventListener("touchstart",this.events.touchstart):this.nodes.container.removeEventListener("mousedown",this.events.down),this.nodes.container.classList.add(this.config.classes.disabled),this.disabled=!0)},Rangeable.prototype.bind=function(){this.events={start:this.start.bind(this),move:this.move.bind(this),stop:this.stop.bind(this),update:this.update.bind(this),reset:this.reset.bind(this),set:this.native.bind(this),key:this.keydown.bind(this)},this.events.scroll=this.throttle(this.events.update,100),this.events.resize=this.throttle(this.events.update,50),document.addEventListener("scroll",this.events.scroll,!1),window.addEventListener("resize",this.events.resize,!1),this.double&&document.addEventListener("keydown",this.events.key,!1),this.nodes.container.addEventListener(this.touch?"touchstart":"mousedown",this.events.start,!1),this.input.addEventListener("input",this.events.set),this.input.form&&this.input.form.addEventListener("reset",this.events.reset,!1)},Rangeable.prototype.unbind=function(){document.removeEventListener("scroll",this.events.scroll),window.removeEventListener("resize",this.events.resize),this.double&&document.removeEventListener("keydown",this.events.key),this.nodes.container.removeEventListener(this.touch?"touchstart":"mousedown",this.events.start),this.input.removeEventListener("input",this.events.set),this.input.form&&this.input.form.removeEventListener("reset",this.events.reset),this.events=null},Rangeable.prototype.destroy=function(){this.input.rangeable&&(this.unbind(),this.input.classList.remove(this.config.classes.input),this.nodes.container.parentNode.replaceChild(this.input,this.nodes.container),delete this.input.rangeable,this.initialised=!1)},Rangeable.prototype.createElement=function(h,i){var j=document.createElement(h);return i&&j.classList.add(i),j},Rangeable.prototype.isFunction=function(h){return h&&"function"==typeof h},Rangeable.prototype.throttle=function(h,i,j){var k;return function(){if(j=j||this,!k)return h.apply(j,arguments),k=!0,setTimeout(function(){k=!1},i)}}; \ No newline at end of file diff --git a/package.json b/package.json index 55911c9..7ad21a0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "rangeable", - "version": "0.0.10", - "description": "A small dependency-free lib to transform range elements into something prettier and more configurable.", + "version": "0.0.11", + "description": "A dependency-free, responsive and touch-enabled javascript range slider.", "main": "dist/rangeable.min.js", "scripts": { "test": "./node_modules/.bin/karma start --single-run --reporters=progress" diff --git a/src/index.js b/src/index.js index 4eb23d3..e5bf855 100644 --- a/src/index.js +++ b/src/index.js @@ -5,7 +5,7 @@ * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. * - * Version: 0.0.10 + * Version: 0.0.11 * */ class Rangeable { @@ -22,7 +22,7 @@ class Rangeable { tooltip: "rangeable-tooltip", track: "rangeable-track", multiple: "rangeable-multiple", - disabled: "rangeable-disabled", + disabled: "rangeable-disabled" } }; @@ -31,17 +31,18 @@ class Rangeable { input = document.querySelector(input); } - this.input = input; - this.config = Object.assign({}, defaultConfig, config); + this.input = input; + this.config = Object.assign({}, defaultConfig, config); - this.mouseAxis = { x: "clientX", y: "clientY" }; - this.trackSize = { x: "width", y: "height" }; - this.trackPos = { x: "left", y: "top" }; - this.lastPos = 0; - - this.double = this.config.type === "double" || Array.isArray(this.config.value); - - this.touch = "ontouchstart" in window || (window.DocumentTouch && document instanceof DocumentTouch); + this.mouseAxis = { x: "clientX", y: "clientY" }; + this.trackSize = { x: "width", y: "height" }; + this.trackPos = { x: "left", y: "top" }; + this.lastPos = 0; + this.double = + this.config.type === "double" || Array.isArray(this.config.value); + this.touch = + "ontouchstart" in window || + (window.DocumentTouch && document instanceof DocumentTouch); this.init(); @@ -53,7 +54,7 @@ class Rangeable { * @return {Void} */ init() { - if ( !this.input.rangeable ) { + if (!this.input.rangeable) { const props = { min: 0, max: 100, step: 1, value: this.input.value }; for (let prop in props) { @@ -72,15 +73,17 @@ class Rangeable { this.input.rangeable = this; - if ( this.double ) { - this.input.values = this.config.value ? this.config.value : [this.input.min, this.input.max]; + if (this.double) { + this.input.values = this.config.value + ? this.config.value + : [this.input.min, this.input.max]; this.input.defaultValues = this.input.values.slice(); } else { if (!this.input.defaultValue) { this.input.defaultValue = this.input.value; } } - + this.render(); this.initialised = true; @@ -104,8 +107,11 @@ class Rangeable { track.appendChild(progress); - if ( this.double ) { - handle = [this.createElement("div", c.handle), this.createElement("div", c.handle)]; + if (this.double) { + handle = [ + this.createElement("div", c.handle), + this.createElement("div", c.handle) + ]; tooltip = [ this.createElement("div", c.tooltip), this.createElement("div", c.tooltip), @@ -119,7 +125,9 @@ class Rangeable { node.tabIndex = 1; }); - if ( o.vertical ) { + this.input.tabIndex = -1; + + if (o.vertical) { progress.appendChild(handle[0]); } @@ -148,7 +156,7 @@ class Rangeable { if (o.tooltips) { container.classList.add("has-tooltip"); - if ( typeof o.tooltips === "string" && o.tooltips === "always" ) { + if (typeof o.tooltips === "string" && o.tooltips === "always") { container.classList.add("show-tooltip"); } } @@ -164,7 +172,7 @@ class Rangeable { } reset() { - if ( this.double ) { + if (this.double) { this.input.defaultValues.forEach(this.setValue, this); } else { this.setValue(this.input.defaultValue); @@ -177,67 +185,55 @@ class Rangeable { const max = parseFloat(this.input.max); const step = parseFloat(this.input.step); const rect = this.rects; - const axis = this.touch ? e.touches[0][this.mouseAxis[this.axis]] : e[this.mouseAxis[this.axis]]; - const pos = axis - this.rects.container[this.trackPos[this.axis]]; + const axis = this.touch + ? e.touches[0][this.mouseAxis[this.axis]] + : e[this.mouseAxis[this.axis]]; + const point = axis - this.rects.container[this.trackPos[this.axis]]; const size = rect.container[this.trackSize[this.axis]]; - - if ( e.type === "mousedown" ) { - if ( (!this.double && this.nodes.handle.contains(e.target)) || - (this.double && (this.nodes.handle[0].contains(e.target) || this.nodes.handle[1].contains(e.target))) ) { + + if (e.type === "mousedown") { + if ( + (!this.double && this.nodes.handle.contains(e.target)) || + (this.double && + (this.nodes.handle[0].contains(e.target) || + this.nodes.handle[1].contains(e.target))) + ) { return false; } } // get the position of the cursor over the bar as a percentage let position = this.config.vertical - ? (size - pos) / size * 100 - : pos / size * 100; + ? (size - point) / size * 100 + : point / size * 100; // work out the value from the position - let value = position * (max - min) / 100 + min; + let val = position * (max - min) / 100 + min; // apply granularity (step) - value = Math.ceil(value / step) * step; - - if ( axis >= this.lastPos ) { - value -= step; - } - + val = Math.ceil(val / step) * step; + + if (axis >= this.lastPos) { + val -= step; + } + // prevent change event from firing if slider hasn't moved - if ( parseFloat(value) === parseFloat(this.startValue) ) { + if (parseFloat(val) === parseFloat(this.startValue)) { return false; - } + } let index = false; - if ( this.double ) { + if (this.double) { index = this.activeHandle.index; - - switch(index) { - case 0: - if ( value >= this.input.values[1] ) { - value = this.input.values[1]; - } - break; - case 1: - if ( value <= this.input.values[0] ) { - value = this.input.values[0]; - } - break; + if (!index && val >= this.input.values[1]) { + val = this.input.values[1]; + } else if (index && val <= this.input.values[0]) { + val = this.input.values[0]; } } - this.setValue(value, index); - } - - change() { - // this.onChange(); - } - - touchstart(e) { - // this.nodes.container.removeEventListener("mousedown", this.events.down); - - this.down(e); + this.setValue(val, index); } /** @@ -245,7 +241,7 @@ class Rangeable { * @param {Object} e * @return {Void} */ - down(e) { + start(e) { e.preventDefault(); this.startValue = this.getValue(); @@ -258,17 +254,17 @@ class Rangeable { this.activeHandle = this.getHandle(e); - this.activeHandle.classList.add('active'); + this.activeHandle.classList.add("active"); this.setValueFromPosition(e); - if ( this.touch ) { + if (this.touch) { document.addEventListener("touchmove", this.events.move, false); - document.addEventListener("touchend", this.events.up, false); - document.addEventListener("touchcancel", this.events.up, false); + document.addEventListener("touchend", this.events.stop, false); + document.addEventListener("touchcancel", this.events.stop, false); } else { document.addEventListener("mousemove", this.events.move, false); - document.addEventListener("mouseup", this.events.up, false); + document.addEventListener("mouseup", this.events.stop, false); } } @@ -279,7 +275,9 @@ class Rangeable { */ move(e) { this.setValueFromPosition(e); - this.lastPos = this.touch ? e.touches[0][this.mouseAxis[this.axis]] : e[this.mouseAxis[this.axis]]; + this.lastPos = this.touch + ? e.touches[0][this.mouseAxis[this.axis]] + : e[this.mouseAxis[this.axis]]; } /** @@ -287,30 +285,100 @@ class Rangeable { * @param {Object} e * @return {Void} */ - up(e) { + stop(e) { this.stopValue = this.getValue(); this.nodes.container.classList.remove("dragging"); this.onEnd(); - this.activeHandle.classList.remove('active'); + this.activeHandle.classList.remove("active"); this.activeHandle = false; document.removeEventListener("mousemove", this.events.move); - document.removeEventListener("mouseup", this.events.up); + document.removeEventListener("mouseup", this.events.stop); document.removeEventListener("touchmove", this.events.move); - document.removeEventListener("touchend", this.events.up); - document.removeEventListener("touchcancel", this.events.up); + document.removeEventListener("touchend", this.events.stop); + document.removeEventListener("touchcancel", this.events.stop); - if ( this.startValue !== this.stopValue ) { + if (this.startValue !== this.stopValue) { this.input.dispatchEvent(new Event("change")); } this.startValue = null; } + keydown(e) { + if ( this.double ) { + this.nodes.handle.forEach(node => { + if ( node === document.activeElement ) { + switch(e.key) { + case 'ArrowRight': + case 'ArrowUp': + this.stepUp(node.index); + break; + case 'ArrowLeft': + case 'ArrowDown': + this.stepDown(node.index); + break; + } + } + }); + } + } + + stepUp(index) { + const step = parseFloat(this.input.step); + + let val = this.getValue(); + + if ( this.double && index !== undefined ) { + val = val[index]; + } + + let newval = this.limit(parseFloat(val) + step, index); + + this.setValue(newval, index); + } + + stepDown(index) { + const step = parseFloat(this.input.step); + + let val = this.getValue(); + + if ( this.double && index !== undefined ) { + val = val[index]; + } + + let newval = this.limit(parseFloat(val) - step, index); + + this.setValue(newval, index); + } + + limit(value, index) { + const el = this.input; + let min = parseFloat(el.min); + let max = parseFloat(el.max); + let inval = this.getValue(); + + if ( this.double && index !== undefined ) { + if (!index && value >= el.values[1]) { + value = el.values[1]; + } else if (index && value <= el.values[0]) { + value = el.values[0]; + } + } + + if ( value >= max ) { + value = max; + } else if ( value <= min ) { + value = min; + } + + return value; + } + /** * Recache the dimensions * @return {Void} @@ -318,12 +386,12 @@ class Rangeable { recalculate() { let handle = []; - if ( this.double ) { + if (this.double) { this.nodes.handle.forEach((node, i) => { handle[i] = node.getBoundingClientRect(); }); } else { - handle = this.nodes.handle.getBoundingClientRect() + handle = this.nodes.handle.getBoundingClientRect(); } this.rects = { @@ -342,11 +410,11 @@ class Rangeable { this.accuracy = 0; // detect float - if ( this.input.step.includes(".") ) { - this.accuracy = (this.input.step.split('.')[1] || []).length; + if (this.input.step.includes(".")) { + this.accuracy = (this.input.step.split(".")[1] || []).length; } - if ( this.double ) { + if (this.double) { this.input.values.forEach((val, i) => { this.setValue(val, i); }); @@ -392,37 +460,41 @@ class Rangeable { value = this.parseValue(value); - if ( this.double && index === undefined ) { + if (this.double && index === undefined) { return false; } - if ( this.double ) { + if (this.double) { handle = this.activeHandle ? this.activeHandle : nodes.handle[index]; } - const doChange = this.initialised && (value !== this.input.value || this.nativeEvent); + const doChange = + this.initialised && (value !== this.input.value || this.nativeEvent); // update the value - if ( this.double ) { + if (this.double) { const values = this.input.values; values[index] = value; - if ( this.config.tooltips ) { + if (this.config.tooltips) { // update the node so we can get the width / height nodes.tooltip[index].textContent = value; // check if tips are intersecting... - const intersecting = this.tipsIntersecting(); + const a = nodes.tooltip[0].getBoundingClientRect(); + const b = nodes.tooltip[1].getBoundingClientRect(); + const intersect = !( a.right < b.left || a.left > b.right || a.bottom < b.top || a.top > b.bottom ); // ... and set the className where appropriate - nodes.container.classList.toggle("combined-tooltip", intersecting); + nodes.container.classList.toggle("combined-tooltip", intersect); - if ( intersecting ) { + if (intersect) { // Format the combined tooltip. // Only show single value if they both match, otherwise show both seperated by a hyphen - nodes.tooltip[2].textContent = values[0] === values[1] ? values[0] : `${values[0]} - ${values[1]}`; + nodes.tooltip[2].textContent = + values[0] === values[1] ? values[0] : `${values[0]} - ${values[1]}`; } - } + } } else { this.input.value = value; nodes.tooltip.textContent = value; @@ -431,10 +503,10 @@ class Rangeable { // set bar size this.setPosition(value, index); - if ( doChange ) { + if (doChange) { this.onChange(); - if ( !this.nativeEvent ) { + if (!this.nativeEvent) { this.input.dispatchEvent(new Event("input")); } @@ -455,12 +527,14 @@ class Rangeable { setPosition(value) { let width; - if ( this.double ) { + if (this.double) { let start = this.getPosition(this.input.values[0]); let end = this.getPosition(this.input.values[1]); // set the start point of the bar - this.nodes.progress.style[this.config.vertical?"bottom":"left"] = `${start}px`; + this.nodes.progress.style[ + this.config.vertical ? "bottom" : "left" + ] = `${start}px`; width = end - start; } else { @@ -480,19 +554,9 @@ class Rangeable { const min = parseFloat(this.input.min); const max = parseFloat(this.input.max); - return (value - min) / (max - min) * this.rects.container[this.trackSize[this.axis]]; - } - - /** - * Check whether the tooltips are colliding - * @return {Boolean} - */ - tipsIntersecting() { - const tips = this.nodes.tooltip; - const a = tips[0].getBoundingClientRect(); - const b = tips[1].getBoundingClientRect(); - - return !(a.right < b.left || a.left > b.right || a.bottom < b.top || a.top > b.bottom); + return ( + (value - min) / (max - min) * this.rects.container[this.trackSize[this.axis]] + ); } /** @@ -501,19 +565,23 @@ class Rangeable { * @return {Obejct} HTMLElement */ getHandle(e) { - if ( !this.double ) { + if (!this.double) { return this.nodes.handle; } const r = this.rects; - const distA = Math.abs(e[this.mouseAxis[this.axis]] - r.handle[0][this.trackPos[this.axis]]); - const distB = Math.abs(e[this.mouseAxis[this.axis]] - r.handle[1][this.trackPos[this.axis]]); + const distA = Math.abs( + e[this.mouseAxis[this.axis]] - r.handle[0][this.trackPos[this.axis]] + ); + const distB = Math.abs( + e[this.mouseAxis[this.axis]] - r.handle[1][this.trackPos[this.axis]] + ); const handle = e.target.closest(`.${this.config.classes.handle}`); - if ( handle ) { + if (handle) { return handle; } else { - if ( distA > distB ) { + if (distA > distB) { return this.nodes.handle[1]; } else { return this.nodes.handle[0]; @@ -521,28 +589,6 @@ class Rangeable { } } - /** - * Destroy the instance - * @return {Void} - */ - destroy() { - if ( this.input.rangeable ) { - // remove all event events - this.unbind(); - - // remove the className from the input - this.input.classList.remove(this.config.classes.input); - - // kill all nodes - this.nodes.container.parentNode.replaceChild(this.input, this.nodes.container); - - // remove the reference from the input - delete(this.input.rangeable); - - this.initialised = false; - } - } - onInit() { if (this.isFunction(this.config.onInit)) { this.config.onInit.call(this, this.getValue()); @@ -566,25 +612,32 @@ class Rangeable { this.config.onEnd.call(this, this.getValue()); } } - + enable() { - if ( this.disabled ) { - if ( this.touch ) { - this.nodes.container.addEventListener("touchstart", this.events.touchstart, false); + if (this.disabled) { + if (this.touch) { + this.nodes.container.addEventListener( + "touchstart", + this.events.touchstart, + false + ); } else { this.nodes.container.addEventListener("mousedown", this.events.down); } this.nodes.container.classList.remove(this.config.classes.disabled); - + this.disabled = false; } } - + disable() { - if ( !this.disabled ) { - if ( this.touch ) { - this.nodes.container.removeEventListener("touchstart", this.events.touchstart); + if (!this.disabled) { + if (this.touch) { + this.nodes.container.removeEventListener( + "touchstart", + this.events.touchstart + ); } else { this.nodes.container.removeEventListener("mousedown", this.events.down); } @@ -593,18 +646,17 @@ class Rangeable { this.disabled = true; } - } + } bind() { this.events = { - down: this.down.bind(this), - touchstart: this.touchstart.bind(this), + start: this.start.bind(this), move: this.move.bind(this), - up: this.up.bind(this), + stop: this.stop.bind(this), update: this.update.bind(this), - change: this.change.bind(this), reset: this.reset.bind(this), set: this.native.bind(this), + key: this.keydown.bind(this), }; this.events.scroll = this.throttle(this.events.update, 100); @@ -616,17 +668,19 @@ class Rangeable { // throttle the resize callback for performance window.addEventListener("resize", this.events.resize, false); - // detect native change event - this.input.addEventListener("change", this.events.change, false); - - if ( this.touch ) { - this.nodes.container.addEventListener("touchstart", this.events.touchstart, false); - } else { - this.nodes.container.addEventListener("mousedown", this.events.down); + if ( this.double ) { + document.addEventListener("keydown", this.events.key, false); } + // touchstart/mousedown + this.nodes.container.addEventListener( + this.touch ? "touchstart" : "mousedown", + this.events.start, + false + ); + // listen for native input to allow keyboard control on focus - this.input.addEventListener('input', this.events.set); + this.input.addEventListener("input", this.events.set); // detect form reset if (this.input.form) { @@ -641,17 +695,17 @@ class Rangeable { // throttle the resize callback for performance window.removeEventListener("resize", this.events.resize); - // detect native change event - this.input.removeEventListener("change", this.events.change); - - if ( this.touch ) { - this.nodes.container.removeEventListener("touchstart", this.events.touchstart); - } else { - this.nodes.container.removeEventListener("mousedown", this.events.down); + if ( this.double ) { + document.removeEventListener("keydown", this.events.key); } + this.nodes.container.removeEventListener( + this.touch ? "touchstart" : "mousedown", + this.events.start + ); + // listen for native input to allow keyboard control on focus - this.input.removeEventListener('input', this.events.set); + this.input.removeEventListener("input", this.events.set); // detect form reset if (this.input.form) { @@ -660,7 +714,32 @@ class Rangeable { this.events = null; } - + + /** + * Destroy the instance + * @return {Void} + */ + destroy() { + if (this.input.rangeable) { + // remove all event events + this.unbind(); + + // remove the className from the input + this.input.classList.remove(this.config.classes.input); + + // kill all nodes + this.nodes.container.parentNode.replaceChild( + this.input, + this.nodes.container + ); + + // remove the reference from the input + delete this.input.rangeable; + + this.initialised = false; + } + } + /** * Create DOM element helper * @param {String} a nodeName @@ -669,7 +748,7 @@ class Rangeable { */ createElement(type, obj) { const el = document.createElement(type); - if ( obj ) { + if (obj) { el.classList.add(obj); } return el;