UNPKG

rangetouch

Version:

A super tiny library to make input type='range' sliders work better on touch devices

1 lines 16.5 kB
{"version":3,"sources":["src/js/rangetouch.js","src/js/config.js","src/js/utils/css.js","src/js/utils/events.js","src/js/utils/is.js","src/js/utils/numbers.js"],"names":["_classCallCheck","instance","Constructor","TypeError","_defineProperties","target","props","i","length","descriptor","enumerable","configurable","writable","Object","defineProperty","key","_createClass","protoProps","staticProps","prototype","_defineProperty","obj","value","ownKeys","object","enumerableOnly","keys","getOwnPropertySymbols","symbols","filter","sym","getOwnPropertyDescriptor","push","apply","_objectSpread2","arguments","source","forEach","getOwnPropertyDescriptors","defineProperties","defaults","addCSS","thumbWidth","watch","matches","element","selector","Array","from","document","querySelectorAll","includes","this","call","trigger","type","event","Event","bubbles","dispatchEvent","getConstructor","input","constructor","instanceOf","isNullOrUndefined","isObject","isNumber","Number","isNaN","isString","String","isBoolean","Boolean","isFunction","Function","isArray","isNodeList","NodeList","isElement","Element","isEvent","isEmpty","is","nullOrUndefined","number","string","boolean","function","array","nodeList","empty","getDecimalPlaces","match","concat","Math","max","round","step","places","parseFloat","toFixed","RangeTouch","a","options","querySelector","rangeTouch","config","init","enabled","style","userSelect","webKitUserSelect","touchAction","listeners","toggle","b","method","set","percent","touch","changedTouches","min","getAttribute","clientRect","getBoundingClientRect","width","clientX","left","disabled","preventDefault","get","targets","observer","MutationObserver","mutations","mutation","addedNodes","node","observe","body","childList","subtree","map","t","documentElement"],"mappings":"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","file":"rangetouch.mjs","sourcesContent":["// ==========================================================================\n// rangetouch.js v2.0.0\n// Making <input type=\"range\"> work on touch devices\n// https://github.com/sampotts/rangetouch\n// License: The MIT License (MIT)\n// ==========================================================================\n\nimport defaults from './config';\nimport { matches } from './utils/css';\nimport { trigger } from './utils/events';\nimport is from './utils/is';\nimport { round } from './utils/numbers';\n\nclass RangeTouch {\n /**\n * Setup a new instance\n * @param {String|Element} target\n * @param {Object} options\n */\n constructor(target, options) {\n if (is.element(target)) {\n // An Element is passed, use it directly\n this.element = target;\n } else if (is.string(target)) {\n // A CSS Selector is passed, fetch it from the DOM\n this.element = document.querySelector(target);\n }\n\n if (!is.element(this.element) || !is.empty(this.element.rangeTouch)) {\n return;\n }\n\n this.config = { ...defaults, ...options };\n\n this.init();\n }\n\n static get enabled() {\n return 'ontouchstart' in document.documentElement;\n }\n\n /**\n * Setup multiple instances\n * @param {String|Element|NodeList|Array} target\n * @param {Object} options\n */\n static setup(target, options = {}) {\n let targets = null;\n\n if (is.empty(target) || is.string(target)) {\n targets = Array.from(document.querySelectorAll(is.string(target) ? target : 'input[type=\"range\"]'));\n } else if (is.element(target)) {\n targets = [target];\n } else if (is.nodeList(target)) {\n targets = Array.from(target);\n } else if (is.array(target)) {\n targets = target.filter(is.element);\n }\n\n if (is.empty(targets)) {\n return null;\n }\n\n const config = { ...defaults, ...options };\n\n if (is.string(target) && config.watch) {\n // Create an observer instance\n const observer = new MutationObserver(mutations => {\n Array.from(mutations).forEach(mutation => {\n Array.from(mutation.addedNodes).forEach(node => {\n if (!is.element(node) || !matches(node, target)) {\n return;\n }\n\n // eslint-disable-next-line no-unused-vars\n const range = new RangeTouch(node, config);\n });\n });\n });\n\n // Pass in the target node, as well as the observer options\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n });\n }\n\n return targets.map(t => new RangeTouch(t, options));\n }\n\n init() {\n // Bail if not a touch enabled device\n if (!RangeTouch.enabled) {\n return;\n }\n\n // Add useful CSS\n if (this.config.addCSS) {\n this.element.style.userSelect = 'none';\n this.element.style.webKitUserSelect = 'none';\n this.element.style.touchAction = 'manipulation';\n }\n\n this.listeners(true);\n\n this.element.rangeTouch = this;\n }\n\n destroy() {\n // Bail if not a touch enabled device\n if (!RangeTouch.enabled) {\n return;\n }\n\n // Remove useful CSS\n if (this.config.addCSS) {\n this.element.style.userSelect = '';\n this.element.style.webKitUserSelect = '';\n this.element.style.touchAction = '';\n }\n\n this.listeners(false);\n\n this.element.rangeTouch = null;\n }\n\n listeners(toggle) {\n const method = toggle ? 'addEventListener' : 'removeEventListener';\n\n // Listen for events\n ['touchstart', 'touchmove', 'touchend'].forEach(type => {\n this.element[method](type, event => this.set(event), false);\n });\n }\n\n /**\n * Get the value based on touch position\n * @param {Event} event\n */\n get(event) {\n if (!RangeTouch.enabled || !is.event(event)) {\n return null;\n }\n\n const input = event.target;\n const touch = event.changedTouches[0];\n const min = parseFloat(input.getAttribute('min')) || 0;\n const max = parseFloat(input.getAttribute('max')) || 100;\n const step = parseFloat(input.getAttribute('step')) || 1;\n const delta = max - min;\n\n // Calculate percentage\n let percent;\n const clientRect = input.getBoundingClientRect();\n const thumbWidth = ((100 / clientRect.width) * (this.config.thumbWidth / 2)) / 100;\n\n // Determine left percentage\n percent = (100 / clientRect.width) * (touch.clientX - clientRect.left);\n\n // Don't allow outside bounds\n if (percent < 0) {\n percent = 0;\n } else if (percent > 100) {\n percent = 100;\n }\n\n // Factor in the thumb offset\n if (percent < 50) {\n percent -= (100 - percent * 2) * thumbWidth;\n } else if (percent > 50) {\n percent += (percent - 50) * 2 * thumbWidth;\n }\n\n // Find the closest step to the mouse position\n return min + round(delta * (percent / 100), step);\n }\n\n /**\n * Update range value based on position\n * @param {Event} event\n */\n set(event) {\n if (!RangeTouch.enabled || !is.event(event) || event.target.disabled) {\n return;\n }\n\n // Prevent text highlight on iOS\n event.preventDefault();\n\n // Set value\n // eslint-disable-next-line no-param-reassign\n event.target.value = this.get(event);\n\n // Trigger event\n trigger(event.target, event.type === 'touchend' ? 'change' : 'input');\n }\n}\n\nexport default RangeTouch;\n","const defaults = {\n addCSS: true, // Add CSS to the element to improve usability (required here or in your CSS!)\n thumbWidth: 15, // The width of the thumb handle\n watch: true, // Watch for new elements that match a string target\n};\n\nexport default defaults;","// Element matches a selector\nexport function matches(element, selector) {\n const prototype = { Element };\n\n function match() {\n return Array.from(document.querySelectorAll(selector)).includes(this);\n }\n\n const method =\n prototype.matches ||\n prototype.webkitMatchesSelector ||\n prototype.mozMatchesSelector ||\n prototype.msMatchesSelector ||\n match;\n\n return method.call(element, selector);\n}\n\nexport default { matches };\n","// Trigger event\nexport function trigger(element, type) {\n if (!element || !type) {\n return;\n }\n\n // Create and dispatch the event\n const event = new Event(type, { bubbles: true });\n\n // Dispatch the event\n element.dispatchEvent(event);\n}\n\nexport default { trigger };\n","// ==========================================================================\n// Type checking utils\n// ==========================================================================\n\nconst getConstructor = input => (input !== null && typeof input !== 'undefined' ? input.constructor : null);\nconst instanceOf = (input, constructor) => Boolean(input && constructor && input instanceof constructor);\n\nconst isNullOrUndefined = input => input === null || typeof input === 'undefined';\nconst isObject = input => getConstructor(input) === Object;\nconst isNumber = input => getConstructor(input) === Number && !Number.isNaN(input);\nconst isString = input => getConstructor(input) === String;\nconst isBoolean = input => getConstructor(input) === Boolean;\nconst isFunction = input => getConstructor(input) === Function;\nconst isArray = input => Array.isArray(input);\nconst isNodeList = input => instanceOf(input, NodeList);\nconst isElement = input => instanceOf(input, Element);\nconst isEvent = input => instanceOf(input, Event);\nconst isEmpty = input =>\n isNullOrUndefined(input) ||\n ((isString(input) || isArray(input) || isNodeList(input)) && !input.length) ||\n (isObject(input) && !Object.keys(input).length);\n\nexport default {\n nullOrUndefined: isNullOrUndefined,\n object: isObject,\n number: isNumber,\n string: isString,\n boolean: isBoolean,\n function: isFunction,\n array: isArray,\n nodeList: isNodeList,\n element: isElement,\n event: isEvent,\n empty: isEmpty,\n};\n","// Get the number of decimal places\nexport function getDecimalPlaces(value) {\n const match = `${value}`.match(/(?:\\.(\\d+))?(?:[eE]([+-]?\\d+))?$/);\n\n if (!match) {\n return 0;\n }\n\n return Math.max(\n 0,\n // Number of digits right of decimal point.\n (match[1] ? match[1].length : 0) -\n // Adjust for scientific notation.\n (match[2] ? +match[2] : 0),\n );\n}\n\n// Round to the nearest step\nexport function round(number, step) {\n if (step < 1) {\n const places = getDecimalPlaces(step);\n return parseFloat(number.toFixed(places));\n }\n return Math.round(number / step) * step;\n}\n\nexport default { getDecimalPlaces, round };\n"]}