Repository: Stryzhevskyi/rangeSlider
Branch: master
Commit: 1284a994e108
Files: 19
Total size: 61.1 KB
Directory structure:
gitextract_68uuadx6/
├── .eslintrc.json
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ └── bug_report.md
│ └── workflows/
│ └── publish-from-tag.yaml
├── .gitignore
├── .jshintrc
├── .npmignore
├── LICENSE.md
├── README.md
├── bower.json
├── example/
│ ├── index-esm.html
│ ├── index-umd.html
│ └── range-slider-flat.css
├── package.json
├── postcss.config.js
├── src/
│ ├── range-slider.css
│ ├── range-slider.js
│ └── utils/
│ ├── dom.js
│ └── functions.js
└── vite.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.json
================================================
{
"extends": "standard",
"rules": {
"semi": 0,
"no-undef": 0,
"operator-linebreak": 0,
"space-before-function-paren": 0,
"indent": ["error", 2],
"prefer-regex-literals": 0,
"quote-props": 0,
"prefer-const": 0,
"dot-notation": 0
}
}
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create bug report
title: ''
labels: bug
assignees: ''
---
**Example**
Provide link to example, use [jsfiddle](https://jsfiddle.net/Stryzhevskyi/rpsa16fn/) as a template.
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Click on '....'
2. Move handle '....'
3. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/workflows/publish-from-tag.yaml
================================================
name: Publish Package
on:
push:
tags:
- '*'
permissions:
id-token: write # Required for OIDC
contents: read
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm run build
- run: npm publish
================================================
FILE: .gitignore
================================================
node_modules
.idea/
.directory
.history/
.vscode/
dist/
================================================
FILE: .jshintrc
================================================
{
"node": true,
"browser": true,
"esnext": true,
"bitwise": true,
"camelcase": true,
"curly": true,
"eqeqeq": true,
"immed": true,
"indent": 2,
"latedef": true,
"newcap": true,
"noarg": true,
"quotmark": "single",
"regexp": true,
"undef": true,
"unused": true,
"strict": true,
"trailing": true,
"smarttabs": true,
"globals": {
"jQuery": true,
"define": true
}
}
================================================
FILE: .npmignore
================================================
.eslintrc.json
.git
.github
.gitignore
.history
.jshintrc
.npmignore
bower.json
example
node_modules
postcss.config.js
src
vite.config.js
================================================
FILE: LICENSE.md
================================================
The MIT License (MIT)
Copyright (c) 2015 Serhii
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.
================================================
FILE: README.md
================================================
# rangeSlider
> Simple, small and fast vanilla JavaScript polyfill for the HTML5 `<input type="range">` slider element.
> Forked from [André Ruffert's jQuery plugin](https://github.com/andreruffert/rangeslider.js)
Check out the [examples](http://stryzhevskyi.github.io/rangeSlider/).
* Touchscreen friendly
* Recalculates `onresize`
* Small and fast
* Supports all major browsers
* Buffer progressbar (for downloading progress etc.)
## Install
Install with [npm](https://www.npmjs.com/package/rangeslider-pure):
```
npm install --save rangeslider-pure
```
## Usage
```js
// Initialize a new plugin instance for one element or NodeList of elements.
const slider = document.querySelector('input[type="range"]');
rangeSlider.create(slider, {
polyfill: true, // Boolean, if true, custom markup will be created
root: document,
rangeClass: 'rangeSlider',
disabledClass: 'rangeSlider--disabled',
fillClass: 'rangeSlider__fill',
bufferClass: 'rangeSlider__buffer',
handleClass: 'rangeSlider__handle',
startEvent: ['mousedown', 'touchstart', 'pointerdown'],
moveEvent: ['mousemove', 'touchmove', 'pointermove'],
endEvent: ['mouseup', 'touchend', 'pointerup'],
vertical: false, // Boolean, if true slider will be displayed in vertical orientation
min: null, // Number, 0
max: null, // Number, 100
step: null, // Number, 1
value: null, // Number, center of slider
buffer: null, // Number, in percent, 0 by default
stick: null, // [Number stickTo, Number stickRadius] : use it if handle should stick to ${stickTo}-th value in ${stickRadius}
borderRadius: 10, // Number, if you're using buffer + border-radius in css
onInit: function () {
console.info('onInit')
},
onSlideStart: function (value, percent, position) {
console.info('onSlideStart', 'value: ' + value, 'percent: ' + percent, 'position: ' + position);
},
onSlide: function (value, percent, position) {
console.log('onSlide', 'value: ' + value, 'percent: ' + percent, 'position: ' + position);
},
onSlideEnd: function (value, percent, position) {
console.warn('onSlideEnd', 'value: ' + value, 'percent: ' + percent, 'position: ' + position);
}
});
// update position
const triggerEvents = true; // or false
slider.rangeSlider.update({
min: 0,
max: 20,
step: 0.5,
value: 1.5,
buffer: 70
}, triggerEvents);
```
```html
<input
type="range"
min="0"
max="100"
step="1"
data-buffer="60" />
```
### Internal APIs:
```js
/*
* @see src/utils/dom.js
*/
RangeSlider.dom;
/*
* @see src/utils/functions.js
*/
RangeSlider.functions;
RangeSlider.version;
```
Use the [JSFiddle](https://jsfiddle.net/Stryzhevskyi/rpsa16fn/) template for issues.
Alternative template on [StackBlitz](https://stackblitz.com/edit/rangeslider-pure-example).
## License
MIT
================================================
FILE: bower.json
================================================
{
"name": "rangeslider-pure",
"version": "0.5.0",
"homepage": "https://github.com/Stryzhevskyi/rangeSlider",
"authors": [
"Stryzhevskyi (https://github.com/Stryzhevskyi)"
],
"repository": {
"type": "git",
"url": "git@github.com:Stryzhevskyi/rangeSlider.git"
},
"description": "Simple, small and fast vanilla JavaScript polyfill for the HTML5 <input type=\"range\"> slider element",
"main": [
"dist/range-slider.js",
"dist/range-slider.css"
],
"license": "MIT",
"keywords": [
"input",
"range",
"slider",
"rangeSlider",
"rangeSlider.js",
"polyfill",
"vanilla",
"pure"
],
"ignore": [
"**/.*",
"*.json",
"src",
"example",
"node_modules",
"bower_components",
"gulpfile.js"
],
"dependencies": {}
}
================================================
FILE: example/index-esm.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>range-slider.js</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- range-slider.css is injected via JS module in dev; link dist/range-slider.css when using the built bundle -->
<!--<link rel="stylesheet" href="../dist/range-slider.css">-->
<!--<link rel="stylesheet" href="range-slider-flat.css">-->
<style>
*,
*:before,
*:after {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html {
color: #404040;
font-family: Helvetica, arial, sans-serif;
}
body {
padding: 50px 20px;
margin: 0 auto;
max-width: 800px;
}
output {
display: block;
font-size: 30px;
font-weight: bold;
text-align: center;
margin: 30px 0;
width: 100%;
}
</style>
</head>
<body>
<div>
<h2>Comparison to native element</h2>
<input type="range" min="0" max="5" data-rangeSlider title="Slide me!">
<br>
<br>
<input type="range" min="0" max="5" style="width:100%;" title="Slide me!">
<output></output>
</div>
<br>
<br>
<div>
<h2>Negative attributes</h2>
<input type="range" min="-20" max="20" data-rangeSlider title="">
<output></output>
</div>
<br>
<br>
<div>
<h2>Vertical mode</h2>
<div style="height: 200px;">
<input type="range" id="vertical">
<output></output>
</div>
</div>
<br>
<br>
<div>
<h2>Floating point boundaries</h2>
<div>
<input type="range" min="10" max="11" step="0.1" value="10.2" data-rangeSlider>
<output></output>
</div>
<div>
<input type="range" min="0.1" max="3" step="0.1" value="0.5" data-rangeSlider>
<output></output>
</div>
<div>
<input type="range" min="0.111" max="3.33" step="0.111" value="1.776" data-rangeSlider>
<output></output>
</div>
</div>
<br>
<br>
<div>
<h2>Stick example</h2>
<h4>Handle will stick to Nth number in X radius, 10 and 0.7 in example</h4>
<div>
<input type="range" min="0" max="100" step="0.1" value="0" stick="10 0.7" data-rangeSlider>
<output></output>
</div>
</div>
<br>
<br>
<div>
<h2><code>value="0"</code></h2>
<input type="range" value="0" data-rangeSlider>
<output></output>
</div>
<br>
<br>
<div id="js-example-disabled">
<h2><code>disabled</code></h2>
<input type="range" min="10" max="100" data-rangeSlider disabled>
<output></output>
<button data-behaviour="toggle">Toggle disabled state</button>
</div>
<br>
<br>
<div>
<h2><code>max="0"</code> disables slider</h2>
<input type="range" max="0" data-rangeSlider>
<output></output>
</div>
<br>
<br>
<div id="js-example-change-value">
<h2>Programmatic value changes</h2>
<input type="range" min="10" max="100" data-rangeSlider>
<output></output>
<input type="number" value="10">
<button>Change value</button>
</div>
<br>
<br>
<br>
<br>
<div id="js-example-buffer">
<h2>Predefined buffer position data-buffer="60"</h2>
<input type="range" min="10" max="100" data-buffer="60" data-rangeSlider>
<output></output>
</div>
<br>
<br>
<br>
<br>
<div id="js-example-buffer-set">
<h2>Update buffer position</h2>
<input type="range" min="10" max="100" data-buffer="60" data-rangeSlider>
<output></output>
<input type="number" value="80"> %
<button>Change value</button>
</div>
<br>
<br>
<br>
<br>
<div id="js-example-update-range">
<h2>Update range</h2>
<input type="range" min="10" max="100" step="10" data-buffer="60" data-rangeSlider>
<output></output>
<button>Update range {min : 0, max : 20, step : 0.5, value : 1.5, buffer : 70}</button>
</div>
<br>
<br>
<br>
<br>
<div id="js-example-destroy">
<h2>Destroy a plugin instance</h2>
<input type="range" min="10" max="1000" step="10" value="500" data-rangeSlider>
<output></output>
<button data-behaviour="destroy">Destroy</button>
<button data-behaviour="initialize">Initialize</button>
</div>
<br>
<br>
<div id="js-example-hidden">
<h2>Consider initialization and update of hidden elements</h2>
<div style="display:none">
<input type="range" min="10" max="100" data-rangeSlider>
<output></output>
</div>
<button data-behaviour="toggle">Toggle visibility</button>
</div>
<br>
<br>
<div id="js-example-hidden-native">
<h2>Combination with native <code><details></code> element</h2>
<details>
<summary>Toggle</summary>
<br>
<br>
<input type="range" min="10" max="100" value="20" data-rangeSlider>
<output></output>
</details>
</div>
<script type="module">
import rangeSlider from '/src/range-slider.js';
(function () {
var selector = '[data-rangeSlider]',
elements = document.querySelectorAll(selector);
// Example functionality to demonstrate a value feedback
function valueOutput(element) {
var value = element.value,
output = element.parentNode.getElementsByTagName('output')[0];
output.innerHTML = value;
}
for (var i = elements.length - 1; i >= 0; i--) {
valueOutput(elements[i]);
}
Array.prototype.slice.call(document.querySelectorAll('input[type="range"]')).forEach(function (el) {
el.addEventListener('input', function (e) {
valueOutput(e.target);
}, false);
});
// Example functionality to demonstrate disabled functionality
var toggleBtnDisable = document.querySelector('#js-example-disabled button[data-behaviour="toggle"]');
toggleBtnDisable.addEventListener('click', function (e) {
var inputRange = toggleBtnDisable.parentNode.querySelector('input[type="range"]');
console.log(inputRange);
if (inputRange.disabled) {
inputRange.disabled = false;
}
else {
inputRange.disabled = true;
}
inputRange.rangeSlider.update();
}, false);
// Example functionality to demonstrate programmatic value changes
var changeValBtn = document.querySelector('#js-example-change-value button');
changeValBtn.addEventListener('click', function (e) {
var inputRange = changeValBtn.parentNode.querySelector('input[type="range"]'),
value = changeValBtn.parentNode.querySelector('input[type="number"]').value,
event = document.createEvent('Event');
event.initEvent('change', true, true);
inputRange.value = value;
inputRange.dispatchEvent(event);
}, false);
// Example functionality to demonstrate programmatic buffer set
var stBufferBtn = document.querySelector('#js-example-buffer-set button');
stBufferBtn.addEventListener('click', function (e) {
var inputRange = stBufferBtn.parentNode.querySelector('input[type="range"]'),
value = stBufferBtn.parentNode.querySelector('input[type="number"]').value;
inputRange.rangeSlider.update({ buffer: value });
}, false);
// Example functionality to demonstrate destroy functionality
var destroyBtn = document.querySelector('#js-example-destroy button[data-behaviour="destroy"]');
destroyBtn.addEventListener('click', function (e) {
var inputRange = destroyBtn.parentNode.querySelector('input[type="range"]');
console.log(inputRange);
inputRange.rangeSlider.destroy();
}, false);
var initBtn = document.querySelector('#js-example-destroy button[data-behaviour="initialize"]');
initBtn.addEventListener('click', function (e) {
var inputRange = initBtn.parentNode.querySelector('input[type="range"]');
rangeSlider.create(inputRange, {});
}, false);
//update range
var updateBtn1 = document.querySelector('#js-example-update-range button');
updateBtn1.addEventListener('click', function (e) {
var inputRange = updateBtn1.parentNode.querySelector('input[type="range"]');
inputRange.rangeSlider.update({ min: 0, max: 20, step: 0.5, value: 1.5, buffer: 70 });
}, false);
var toggleBtn = document.querySelector('#js-example-hidden button[data-behaviour="toggle"]');
toggleBtn.addEventListener('click', function (e) {
var container = e.target.previousElementSibling;
if (container.style.cssText.match(/display[\s:]{1,3}none/)) {
container.style.cssText = '';
} else {
container.style.cssText = 'display: none;';
}
}, false);
// Basic rangeSlider initialization
rangeSlider.create(elements, {
// Callback function
onInit: function () {
},
// Callback function
onSlideStart: function (value, percent, position) {
console.info('onSlideStart', 'value: ' + value, 'percent: ' + percent, 'position: ' + position);
},
// Callback function
onSlide: function (value, percent, position) {
console.log('onSlide', 'value: ' + value, 'percent: ' + percent, 'position: ' + position);
},
// Callback function
onSlideEnd: function (value, percent, position) {
console.warn('onSlideEnd', 'value: ' + value, 'percent: ' + percent, 'position: ' + position);
}
});
rangeSlider.create(document.querySelector('#vertical'), {
vertical: true
});
})();
</script>
</body>
</html>
================================================
FILE: example/index-umd.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>range-slider.js</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- range-slider.css is injected via JS module in dev; link dist/range-slider.css when using the built bundle -->
<link rel="stylesheet" href="/dist/range-slider.css">
<!--<link rel="stylesheet" href="range-slider-flat.css">-->
<style>
*,
*:before,
*:after {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html {
color: #404040;
font-family: Helvetica, arial, sans-serif;
}
body {
padding: 50px 20px;
margin: 0 auto;
max-width: 800px;
}
output {
display: block;
font-size: 30px;
font-weight: bold;
text-align: center;
margin: 30px 0;
width: 100%;
}
</style>
</head>
<body>
<div>
<h2>Comparison to native element</h2>
<input type="range" min="0" max="5" data-rangeSlider title="Slide me!">
<br>
<br>
<input type="range" min="0" max="5" style="width:100%;" title="Slide me!">
<output></output>
</div>
<br>
<br>
<div>
<h2>Negative attributes</h2>
<input type="range" min="-20" max="20" data-rangeSlider title="">
<output></output>
</div>
<br>
<br>
<div>
<h2>Vertical mode</h2>
<div style="height: 200px;">
<input type="range" id="vertical">
<output></output>
</div>
</div>
<br>
<br>
<div>
<h2>Floating point boundaries</h2>
<div>
<input type="range" min="10" max="11" step="0.1" value="10.2" data-rangeSlider>
<output></output>
</div>
<div>
<input type="range" min="0.1" max="3" step="0.1" value="0.5" data-rangeSlider>
<output></output>
</div>
<div>
<input type="range" min="0.111" max="3.33" step="0.111" value="1.776" data-rangeSlider>
<output></output>
</div>
</div>
<br>
<br>
<div>
<h2>Stick example</h2>
<h4>Handle will stick to Nth number in X radius, 10 and 0.15 in example</h4>
<div>
<input type="range" min="0" max="100" step="0.1" value="0" stick="10 0.1" data-rangeSlider>
<output></output>
</div>
</div>
<br>
<br>
<div>
<h2><code>value="0"</code></h2>
<input type="range" value="0" data-rangeSlider>
<output></output>
</div>
<br>
<br>
<div id="js-example-disabled">
<h2><code>disabled</code></h2>
<input type="range" min="10" max="100" data-rangeSlider disabled>
<output></output>
<button data-behaviour="toggle">Toggle disabled state</button>
</div>
<br>
<br>
<div>
<h2><code>max="0"</code> disables slider</h2>
<input type="range" max="0" data-rangeSlider>
<output></output>
</div>
<br>
<br>
<div id="js-example-change-value">
<h2>Programmatic value changes</h2>
<input type="range" min="10" max="100" data-rangeSlider>
<output></output>
<input type="number" value="10">
<button>Change value</button>
</div>
<br>
<br>
<br>
<br>
<div id="js-example-buffer">
<h2>Predefined buffer position data-buffer="60"</h2>
<input type="range" min="10" max="100" data-buffer="60" data-rangeSlider>
<output></output>
</div>
<br>
<br>
<br>
<br>
<div id="js-example-buffer-set">
<h2>Update buffer position</h2>
<input type="range" min="10" max="100" data-buffer="60" data-rangeSlider>
<output></output>
<input type="number" value="80"> %
<button>Change value</button>
</div>
<br>
<br>
<br>
<br>
<div id="js-example-update-range">
<h2>Update range</h2>
<input type="range" min="10" max="100" step="10" data-buffer="60" data-rangeSlider>
<output></output>
<button>Update range {min : 0, max : 20, step : 0.5, value : 1.5, buffer : 70}</button>
</div>
<br>
<br>
<br>
<br>
<div id="js-example-destroy">
<h2>Destroy a plugin instance</h2>
<input type="range" min="10" max="1000" step="10" value="500" data-rangeSlider>
<output></output>
<button data-behaviour="destroy">Destroy</button>
<button data-behaviour="initialize">Initialize</button>
</div>
<br>
<br>
<div id="js-example-hidden">
<h2>Consider initialization and update of hidden elements</h2>
<div style="display:none">
<input type="range" min="10" max="100" data-rangeSlider>
<output></output>
</div>
<button data-behaviour="toggle">Toggle visibility</button>
</div>
<br>
<br>
<div id="js-example-hidden-native">
<h2>Combination with native <code><details></code> element</h2>
<details>
<summary>Toggle</summary>
<br>
<br>
<input type="range" min="10" max="100" value="20" data-rangeSlider>
<output></output>
</details>
</div>
<script src="/dist/range-slider.js"></script>
<script>
var rangeSlider = window.rangeSlider;
console.log('rangeSlider', rangeSlider);
(function () {
var selector = '[data-rangeSlider]',
elements = document.querySelectorAll(selector);
// Example functionality to demonstrate a value feedback
function valueOutput(element) {
var value = element.value,
output = element.parentNode.getElementsByTagName('output')[0];
output.innerHTML = value;
}
for (var i = elements.length - 1; i >= 0; i--) {
valueOutput(elements[i]);
}
Array.prototype.slice.call(document.querySelectorAll('input[type="range"]')).forEach(function (el) {
el.addEventListener('input', function (e) {
valueOutput(e.target);
}, false);
});
// Example functionality to demonstrate disabled functionality
var toggleBtnDisable = document.querySelector('#js-example-disabled button[data-behaviour="toggle"]');
toggleBtnDisable.addEventListener('click', function (e) {
var inputRange = toggleBtnDisable.parentNode.querySelector('input[type="range"]');
console.log(inputRange);
if (inputRange.disabled) {
inputRange.disabled = false;
}
else {
inputRange.disabled = true;
}
inputRange.rangeSlider.update();
}, false);
// Example functionality to demonstrate programmatic value changes
var changeValBtn = document.querySelector('#js-example-change-value button');
changeValBtn.addEventListener('click', function (e) {
var inputRange = changeValBtn.parentNode.querySelector('input[type="range"]'),
value = changeValBtn.parentNode.querySelector('input[type="number"]').value,
event = document.createEvent('Event');
event.initEvent('change', true, true);
inputRange.value = value;
inputRange.dispatchEvent(event);
}, false);
// Example functionality to demonstrate programmatic buffer set
var stBufferBtn = document.querySelector('#js-example-buffer-set button');
stBufferBtn.addEventListener('click', function (e) {
var inputRange = stBufferBtn.parentNode.querySelector('input[type="range"]'),
value = stBufferBtn.parentNode.querySelector('input[type="number"]').value;
inputRange.rangeSlider.update({ buffer: value });
}, false);
// Example functionality to demonstrate destroy functionality
var destroyBtn = document.querySelector('#js-example-destroy button[data-behaviour="destroy"]');
destroyBtn.addEventListener('click', function (e) {
var inputRange = destroyBtn.parentNode.querySelector('input[type="range"]');
console.log(inputRange);
inputRange.rangeSlider.destroy();
}, false);
var initBtn = document.querySelector('#js-example-destroy button[data-behaviour="initialize"]');
initBtn.addEventListener('click', function (e) {
var inputRange = initBtn.parentNode.querySelector('input[type="range"]');
rangeSlider.create(inputRange, {});
}, false);
//update range
var updateBtn1 = document.querySelector('#js-example-update-range button');
updateBtn1.addEventListener('click', function (e) {
var inputRange = updateBtn1.parentNode.querySelector('input[type="range"]');
inputRange.rangeSlider.update({ min: 0, max: 20, step: 0.5, value: 1.5, buffer: 70 });
}, false);
var toggleBtn = document.querySelector('#js-example-hidden button[data-behaviour="toggle"]');
toggleBtn.addEventListener('click', function (e) {
var container = e.target.previousElementSibling;
if (container.style.cssText.match(/display[\s:]{1,3}none/)) {
container.style.cssText = '';
} else {
container.style.cssText = 'display: none;';
}
}, false);
// Basic rangeSlider initialization
rangeSlider.create(elements, {
// Callback function
onInit: function () {
},
// Callback function
onSlideStart: function (value, percent, position) {
console.info('onSlideStart', 'value: ' + value, 'percent: ' + percent, 'position: ' + position);
},
// Callback function
onSlide: function (value, percent, position) {
console.log('onSlide', 'value: ' + value, 'percent: ' + percent, 'position: ' + position);
},
// Callback function
onSlideEnd: function (value, percent, position) {
console.warn('onSlideEnd', 'value: ' + value, 'percent: ' + percent, 'position: ' + position);
}
});
rangeSlider.create(document.querySelector('#vertical'), {
vertical: true
});
})();
</script>
</body>
</html>
================================================
FILE: example/range-slider-flat.css
================================================
.rangeSlider,
.rangeSlider__fill {
background: #7f8c8d;
display: block;
height: 8px;
width: 100%;
-webkit-box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.3);
-moz-box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.3);
box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.5);
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
border-radius: 4px;
}
.rangeSlider {
position: relative;
}
.rangeSlider--disabled {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
opacity: 0.4;
}
.rangeSlider__fill {
background: #FFFFFF;
position: absolute;
top: 0;
}
.rangeSlider__handle {
background: white;
border: 1px solid #ccc;
cursor: pointer;
display: inline-block;
width: 22px;
height: 21px;
position: absolute;
top: -7px;
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(100%, rgba(0, 0, 0, 0.1)));
background-image: -webkit-linear-gradient(rgba(255, 255, 255, 0), rgba(0, 0, 0, 0.1));
background-image: -moz-linear-gradient(rgba(255, 255, 255, 0), rgba(0, 0, 0, 0.1));
background-image: -o-linear-gradient(rgba(255, 255, 255, 0), rgba(0, 0, 0, 0.1));
background-image: linear-gradient(rgba(255, 255, 255, 0), rgba(0, 0, 0, 0.1));
-webkit-box-shadow: 0 0 8px rgba(0, 0, 0, 0.3);
-moz-box-shadow: 0 0 8px rgba(0, 0, 0, 0.3);
box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
-ms-border-radius: 50%;
-o-border-radius: 50%;
border-radius: 50%;
}
.rangeSlider__handle:after {
content: "";
display: block;
width: 10px;
height: 10px;
margin: auto;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, rgba(0, 0, 0, 0.13)), color-stop(100%, rgba(255, 255, 255, 0)));
background-image: -webkit-linear-gradient(rgba(0, 0, 0, 0.13), rgba(255, 255, 255, 0));
background-image: -moz-linear-gradient(rgba(0, 0, 0, 0.13), rgba(255, 255, 255, 0));
background-image: -o-linear-gradient(rgba(0, 0, 0, 0.13), rgba(255, 255, 255, 0));
background-image: linear-gradient(rgba(0, 0, 0, 0.13), rgba(255, 255, 255, 0));
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
-ms-border-radius: 50%;
-o-border-radius: 50%;
border-radius: 50%;
}
.rangeSlider__handle:active {
background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, rgba(0, 0, 0, 0.1)), color-stop(100%, rgba(0, 0, 0, 0.12)));
background-image: -webkit-linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.12));
background-image: -moz-linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.12));
background-image: -o-linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.12));
background-image: linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.12));
outline: none;
}
input[type="range"]:focus + .rangeSlider .rangeSlider__handle {
-webkit-box-shadow: 0 0 8px rgba(142, 68, 173, 0.9);
-moz-box-shadow: 0 0 8px rgba(142, 68, 173, 0.9);
box-shadow: 0 0 8px rgba(142, 68, 173, 0.9);
}
.rangeSlider__buffer {
position: absolute;
top: 2px;
height: 4px;
background: #2c3e50;
border-radius: 2px;
}
================================================
FILE: package.json
================================================
{
"name": "rangeslider-pure",
"title": "range-slider",
"description": "Simple, small and fast vanilla JavaScript polyfill for the HTML5 <input type=\"range\"> slider element",
"version": "0.5.0",
"type": "module",
"main": "dist/range-slider.js",
"module": "dist/range-slider.esm.js",
"types": "dist/range-slider.d.ts",
"exports": {
".": {
"import": "./dist/range-slider.esm.js",
"require": "./dist/range-slider.js"
},
"./dist/range-slider.css": "./dist/range-slider.css"
},
"files": [
"dist",
"LICENSE.md",
"README.md"
],
"scripts": {
"dev": "vite serve",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint --fix src/*.js"
},
"homepage": "https://github.com/Stryzhevskyi/rangeSlider",
"author": {
"name": "Serhii Stryzhevskyi",
"url": "https://github.com/Stryzhevskyi"
},
"repository": {
"type": "git",
"url": "git@github.com:Stryzhevskyi/rangeSlider.git"
},
"keywords": [
"input",
"range",
"slider",
"rangeslider",
"rangeslider.js",
"polyfill",
"browser",
"pure",
"vanilla"
],
"licenses": [
{
"type": "MIT",
"url": "https://github.com/Stryzhevskyi/rangeSlider/blob/master/LICENSE.md"
}
],
"bugs": {
"url": "https://github.com/Stryzhevskyi/rangeSlider/issues"
},
"engines": {
"node": ">=18.0.0"
},
"browserslist": [
"ie >= 9",
"last 3 versions"
],
"devDependencies": {
"autoprefixer": "^10.4.27",
"cssnano": "^7.0.6",
"eslint": "^10.1.0",
"vite": "^8.0.1"
}
}
================================================
FILE: postcss.config.js
================================================
export default {
plugins: {
autoprefixer: {},
cssnano: {},
},
};
================================================
FILE: src/range-slider.css
================================================
.rangeSlider, .rangeSlider__fill {
display: block;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3);
border-radius: 10px;
}
.rangeSlider {
position: relative;
background: #7f8c8d;
}
.rangeSlider__horizontal {
height: 20px;
width: 100%;
}
.rangeSlider__vertical {
height: 100%;
width: 20px;
}
.rangeSlider--disabled {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
opacity: 0.4;
}
.rangeSlider__fill {
background: #16a085;
position: absolute;
}
.rangeSlider__fill__horizontal {
height: 100%;
top: 0;
left: 0;
}
.rangeSlider__fill__vertical {
width: 100%;
bottom: 0;
left: 0;
}
.rangeSlider__handle {
border: 1px solid #ccc;
cursor: pointer;
display: inline-block;
width: 40px;
height: 40px;
position: absolute;
background: white linear-gradient(rgba(255, 255, 255, 0), rgba(0, 0, 0, 0.1));
box-shadow: 0 0 8px rgba(0, 0, 0, 0.3);
border-radius: 50%;
}
.rangeSlider__handle__horizontal {
top: -10px;
}
.rangeSlider__handle__vertical {
left: -10px;
bottom: 0;
}
.rangeSlider__handle:after {
content: "";
display: block;
width: 18px;
height: 18px;
margin: auto;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-image: linear-gradient(rgba(0, 0, 0, 0.13), rgba(255, 255, 255, 0));
border-radius: 50%;
}
.rangeSlider__handle:active {
background-image: linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.12));
}
input[type="range"]:focus + .rangeSlider .rangeSlider__handle {
box-shadow: 0 0 8px rgba(142, 68, 173, 0.9);
}
.rangeSlider__buffer {
position: absolute;
top: 3px;
height: 14px;
background: #2c3e50;
border-radius: 10px;
}
================================================
FILE: src/range-slider.js
================================================
import * as dom from './utils/dom';
import * as func from './utils/functions';
import './range-slider.css';
const newLineAndTabRegexp = /[\n\t]/g;
const MAX_SET_BY_DEFAULT = 100;
const HANDLE_RESIZE_DELAY = 300;
const HANDLE_RESIZE_DEBOUNCE = 50;
const pluginName = 'rangeSlider';
const inputrange = dom.supportsRange();
const defaults = {
polyfill: true,
root: document,
rangeClass: 'rangeSlider',
disabledClass: 'rangeSlider--disabled',
fillClass: 'rangeSlider__fill',
bufferClass: 'rangeSlider__buffer',
handleClass: 'rangeSlider__handle',
startEvent: ['mousedown', 'touchstart', 'pointerdown'],
moveEvent: ['mousemove', 'touchmove', 'pointermove'],
endEvent: ['mouseup', 'touchend', 'pointerup'],
min: null,
max: null,
step: null,
value: null,
buffer: null,
stick: null,
borderRadius: 10,
vertical: false
};
let verticalSlidingFixRegistered = false;
/**
* Plugin
* @param {HTMLElement} element
* @param {this} options
*/
export default class RangeSlider {
constructor(element, options) {
let minSetByDefault;
let maxSetByDefault;
let stepSetByDefault;
let stickAttribute;
let stickValues;
RangeSlider.instances.push(this);
this.element = element;
this.options = func.simpleExtend(defaults, options);
this.polyfill = this.options.polyfill;
this.vertical = this.options.vertical;
this.onInit = this.options.onInit;
this.onSlide = this.options.onSlide;
this.onSlideStart = this.options.onSlideStart;
this.onSlideEnd = this.options.onSlideEnd;
this.onSlideEventsCount = -1;
this.isInteractsNow = false;
this.needTriggerEvents = false;
this._addVerticalSlideScrollFix();
// Plugin should only be used as a polyfill
if (!this.polyfill) {
// Input range support?
if (inputrange) {
return;
}
}
this.options.buffer = this.options.buffer || parseFloat(this.element.getAttribute('data-buffer'));
this.identifier = 'js-' + pluginName + '-' + func.uuid();
this.min = func.getFirstNumberLike(
this.options.min,
parseFloat(this.element.getAttribute('min')),
(minSetByDefault = 0)
);
this.max = func.getFirstNumberLike(
this.options.max,
parseFloat(this.element.getAttribute('max')),
(maxSetByDefault = MAX_SET_BY_DEFAULT)
);
this.value = func.getFirstNumberLike(this.options.value, this.element.value,
parseFloat(this.element.value || this.min + (this.max - this.min) / 2));
this.step = func.getFirstNumberLike(this.options.step,
parseFloat(this.element.getAttribute('step')) || (stepSetByDefault = 1));
this.percent = null;
if (func.isArray(this.options.stick) && this.options.stick.length >= 1) {
this.stick = this.options.stick;
} else if ((stickAttribute = this.element.getAttribute('stick'))) {
stickValues = stickAttribute.split(' ');
if (stickValues.length >= 1) {
this.stick = stickValues.map(parseFloat);
}
}
if (this.stick && this.stick.length === 1) {
this.stick.push(this.step * 1.5);
}
this._updatePercentFromValue();
this.toFixed = this._toFixed(this.step);
let directionClass;
this.container = document.createElement('div');
dom.addClass(this.container, this.options.fillClass);
directionClass = this.vertical ? this.options.fillClass + '__vertical' : this.options.fillClass + '__horizontal';
dom.addClass(this.container, directionClass);
this.handle = document.createElement('div');
dom.addClass(this.handle, this.options.handleClass);
directionClass = this.vertical ?
this.options.handleClass + '__vertical' :
this.options.handleClass + '__horizontal';
dom.addClass(this.handle, directionClass);
this.range = document.createElement('div');
dom.addClass(this.range, this.options.rangeClass);
this.range.id = this.identifier;
const elementTitle = element.getAttribute('title');
if (elementTitle && elementTitle.length > 0) {
this.range.setAttribute('title', elementTitle);
}
if (this.options.bufferClass) {
this.buffer = document.createElement('div');
dom.addClass(this.buffer, this.options.bufferClass);
this.range.appendChild(this.buffer);
directionClass = this.vertical ?
this.options.bufferClass + '__vertical' :
this.options.bufferClass + '__horizontal';
dom.addClass(this.buffer, directionClass);
}
this.range.appendChild(this.container);
this.range.appendChild(this.handle);
directionClass = this.vertical ? this.options.rangeClass + '__vertical' : this.options.rangeClass + '__horizontal';
dom.addClass(this.range, directionClass);
if (func.isNumberLike(this.options.value)) {
this._setValue(this.options.value, true);
this.element.value = this.options.value;
}
if (func.isNumberLike(this.options.buffer)) {
this.element.setAttribute('data-buffer', this.options.buffer);
}
if (func.isNumberLike(this.options.min) || minSetByDefault) {
this.element.setAttribute('min', '' + this.min);
}
if (func.isNumberLike(this.options.max) || maxSetByDefault) {
this.element.setAttribute('max', '' + this.max);
}
if (func.isNumberLike(this.options.step) || stepSetByDefault) {
this.element.setAttribute('step', '' + this.step);
}
dom.insertAfter(this.element, this.range);
// hide the input visually
dom.setCss(this.element, {
'position': 'absolute',
'width': '1px',
'height': '1px',
'overflow': 'hidden',
'opacity': '0'
});
// Store context
this._handleDown = this._handleDown.bind(this);
this._handleMove = this._handleMove.bind(this);
this._handleEnd = this._handleEnd.bind(this);
this._startEventListener = this._startEventListener.bind(this);
this._changeEventListener = this._changeEventListener.bind(this);
this._handleResize = this._handleResize.bind(this);
this._init();
// Attach Events
window.addEventListener('resize', this._handleResize, false);
dom.addEventListeners(this.options.root, this.options.startEvent, this._startEventListener);
// Listen to programmatic value changes
this.element.addEventListener('change', this._changeEventListener, false);
}
/**
* A lightweight plugin wrapper around the constructor,preventing against multiple instantiations
* @param {Element} el
* @param {Object} options
*/
static create(el, options) {
const createInstance = (el) => {
let data = el[pluginName];
// Create a new instance.
if (!data) {
data = new RangeSlider(el, options);
el[pluginName] = data;
}
};
if (el.length) {
Array.prototype.slice.call(el).forEach(function (el) {
createInstance(el);
});
} else {
createInstance(el);
}
}
static _touchMoveScrollHandler (event) {
if (RangeSlider.slidingVertically) {
event.preventDefault();
}
}
/* public methods */
/**
* @param {Object} obj like {min : Number, max : Number, value : Number, step : Number, buffer : [String|Number]}
* @param {Boolean} triggerEvents
* @returns {RangeSlider}
*/
update(obj, triggerEvents) {
if (triggerEvents) {
this.needTriggerEvents = true;
}
if (func.isObject(obj)) {
if (func.isNumberLike(obj.min)) {
this.element.setAttribute('min', '' + obj.min);
this.min = obj.min;
}
if (func.isNumberLike(obj.max)) {
this.element.setAttribute('max', '' + obj.max);
this.max = obj.max;
}
if (func.isNumberLike(obj.step)) {
this.element.setAttribute('step', '' + obj.step);
this.step = obj.step;
this.toFixed = this._toFixed(obj.step);
}
if (func.isNumberLike(obj.buffer)) {
this._setBufferPosition(obj.buffer);
}
if (func.isNumberLike(obj.value)) {
this._setValue(obj.value);
}
}
this._update();
this.onSlideEventsCount = 0;
this.needTriggerEvents = false;
return this;
};
destroy() {
dom.removeAllListenersFromEl(this, this.options.root);
window.removeEventListener('resize', this._handleResize, false);
this.element.removeEventListener('change', this._changeEventListener, false);
this.element.style.cssText = '';
delete this.element[pluginName];
// Remove the generated markup
if (this.range) {
this.range.parentNode.removeChild(this.range);
}
RangeSlider.instances = RangeSlider.instances.filter((plugin) => plugin !== this);
if (!RangeSlider.instances.some((plugin) => plugin.vertical)) {
this._removeVerticalSlideScrollFix();
}
}
/* private methods */
_toFixed(step) {
return (step + '').replace('.', '').length - 1;
}
_init() {
if (this.onInit && typeof this.onInit === 'function') {
this.onInit();
}
this._update(false);
}
_updatePercentFromValue() {
this.percent = (this.value - this.min) / (this.max - this.min);
}
/**
* This method check if this.identifier exists in ev.target's ancestors
* @param ev
* @param data
*/
_startEventListener(ev, data) {
const el = ev.target;
let isEventOnSlider = false;
if (ev.which !== 1 && !('touches' in ev)) {
return;
}
dom.forEachAncestors(
el,
el => (isEventOnSlider = el.id === this.identifier && !dom.hasClass(el, this.options.disabledClass)),
true
);
if (isEventOnSlider) {
this._handleDown(ev, data);
}
}
_changeEventListener(ev, data) {
if (data && data.origin === this.identifier) {
return;
}
const value = ev.target.value;
const pos = this._getPositionFromValue(value);
this._setPosition(pos);
}
_update(triggerEvent) {
const sizeProperty = this.vertical ? 'offsetHeight' : 'offsetWidth';
this.handleSize = dom.getDimension(this.handle, sizeProperty);
this.rangeSize = dom.getDimension(this.range, sizeProperty);
this.maxHandleX = this.rangeSize - this.handleSize;
this.grabX = this.handleSize / 2;
this.position = this._getPositionFromValue(this.value);
// Consider disabled state
if (this.element.disabled) {
dom.addClass(this.range, this.options.disabledClass);
} else {
dom.removeClass(this.range, this.options.disabledClass);
}
this._setPosition(this.position);
if (this.options.bufferClass && this.options.buffer) {
this._setBufferPosition(this.options.buffer);
}
this._updatePercentFromValue();
if (triggerEvent !== false) {
dom.triggerEvent(this.element, 'change', { origin: this.identifier });
}
}
_addVerticalSlideScrollFix() {
if (this.vertical && !verticalSlidingFixRegistered) {
document.addEventListener('touchmove', RangeSlider._touchMoveScrollHandler, { passive: false });
verticalSlidingFixRegistered = true;
}
}
_removeVerticalSlideScrollFix() {
document.removeEventListener('touchmove', RangeSlider._touchMoveScrollHandler);
verticalSlidingFixRegistered = false;
}
_handleResize() {
return func.debounce(() => {
// Simulate resizeEnd event.
func.delay(() => {
this._update();
}, HANDLE_RESIZE_DELAY);
}, HANDLE_RESIZE_DEBOUNCE)();
}
_handleDown(e) {
this.isInteractsNow = true;
e.preventDefault();
dom.addEventListeners(this.options.root, this.options.moveEvent, this._handleMove);
dom.addEventListeners(this.options.root, this.options.endEvent, this._handleEnd);
// If we click on the handle don't set the new position
if ((' ' + e.target.className + ' ').replace(newLineAndTabRegexp, ' ').indexOf(this.options.handleClass) > -1) {
return;
}
const boundingClientRect = this.range.getBoundingClientRect();
const posX = this._getRelativePosition(e);
const rangeX = this.vertical ? boundingClientRect.bottom : boundingClientRect.left;
const handleX = this._getPositionFromNode(this.handle) - rangeX;
const position = posX - this.grabX;
this._setPosition(position);
if (posX >= handleX && posX < handleX + this.options.borderRadius * 2) {
this.grabX = posX - handleX;
}
this._updatePercentFromValue();
}
_handleMove(e) {
const posX = this._getRelativePosition(e);
this.isInteractsNow = true;
e.preventDefault();
this._setPosition(posX - this.grabX);
}
_handleEnd(e) {
e.preventDefault();
dom.removeEventListeners(this.options.root, this.options.moveEvent, this._handleMove);
dom.removeEventListeners(this.options.root, this.options.endEvent, this._handleEnd);
// Ok we're done fire the change event
dom.triggerEvent(this.element, 'change', { origin: this.identifier });
if (this.isInteractsNow || this.needTriggerEvents) {
if (this.onSlideEnd && typeof this.onSlideEnd === 'function') {
this.onSlideEnd(this.value, this.percent, this.position);
}
if (this.vertical) {
RangeSlider.slidingVertically = false;
}
}
this.onSlideEventsCount = 0;
this.isInteractsNow = false;
}
_setPosition(pos) {
let position;
let stickRadius;
let restFromValue;
let stickTo;
// Snapping steps
let value = this._getValueFromPosition(func.between(pos, 0, this.maxHandleX));
// Stick to stick[0] in radius stick[1]
if (this.stick) {
stickTo = this.stick[0];
stickRadius = this.stick[1] || 0.1;
restFromValue = value % stickTo;
if (restFromValue < stickRadius) {
value = value - restFromValue;
} else if (Math.abs(stickTo - restFromValue) < stickRadius) {
value = value - restFromValue + stickTo;
}
}
position = this._getPositionFromValue(value);
// Update ui
if (this.vertical) {
this.container.style.height = (position + this.grabX) + 'px';
this.handle.style['webkitTransform'] = 'translateY(-' + position + 'px)';
this.handle.style['msTransform'] = 'translateY(-' + position + 'px)';
this.handle.style.transform = 'translateY(-' + position + 'px)';
} else {
this.container.style.width = (position + this.grabX) + 'px';
this.handle.style['webkitTransform'] = 'translateX(' + position + 'px)';
this.handle.style['msTransform'] = 'translateX(' + position + 'px)';
this.handle.style.transform = 'translateX(' + position + 'px)';
}
this._setValue(value);
// Update globals
this.position = position;
this.value = value;
this._updatePercentFromValue();
if (this.isInteractsNow || this.needTriggerEvents) {
if (this.onSlideStart && typeof this.onSlideStart === 'function' && this.onSlideEventsCount === 0) {
this.onSlideStart(this.value, this.percent, this.position);
}
if (this.onSlide && typeof this.onSlide === 'function') {
this.onSlide(this.value, this.percent, this.position);
}
if (this.vertical) {
RangeSlider.slidingVertically = true;
}
}
this.onSlideEventsCount++;
}
_setBufferPosition(pos) {
let isPercent = true;
if (isFinite(pos)) {
pos = parseFloat(pos);
} else if (func.isString(pos)) {
if (pos.indexOf('px') > 0) {
isPercent = false;
}
pos = parseFloat(pos);
} else {
console.warn('New position must be XXpx or XX%');
return;
}
if (isNaN(pos)) {
console.warn('New position is NaN');
return;
}
if (!this.options.bufferClass) {
console.warn('You disabled buffer, it\'s className is empty');
return;
}
let bufferSize = isPercent ? pos : (pos / this.rangeSize * 100);
if (bufferSize < 0) {
bufferSize = 0;
}
if (bufferSize > 100) {
bufferSize = 100;
}
this.options.buffer = bufferSize;
let paddingSize = this.options.borderRadius / this.rangeSize * 100;
let bufferSizeWithPadding = bufferSize - paddingSize;
if (bufferSizeWithPadding < 0) {
bufferSizeWithPadding = 0;
}
if (this.vertical) {
this.buffer.style.height = bufferSizeWithPadding + '%';
this.buffer.style.bottom = paddingSize * 0.5 + '%';
} else {
this.buffer.style.width = bufferSizeWithPadding + '%';
this.buffer.style.left = paddingSize * 0.5 + '%';
}
this.element.setAttribute('data-buffer', bufferSize);
}
/**
*
* @param {Element} node
* @returns {*} Returns element position relative to the parent
* @private
*/
_getPositionFromNode(node) {
let i = 0;
while (node !== null) {
i += this.vertical ? node.offsetTop : node.offsetLeft;
node = node.offsetParent;
}
return i;
}
/**
*
* @param {(MouseEvent|TouchEvent)}e
* @returns {number}
*/
_getRelativePosition(e) {
const boundingClientRect = this.range.getBoundingClientRect();
// Get the offset relative to the viewport
const rangeSize = this.vertical ? boundingClientRect.bottom : boundingClientRect.left;
let pageOffset = 0;
const pagePositionProperty = this.vertical ? 'pageY' : 'pageX';
if (typeof e[pagePositionProperty] !== 'undefined') {
pageOffset = (e.touches && e.touches.length) ? e.touches[0][pagePositionProperty] : e[pagePositionProperty];
} else if (typeof e.originalEvent !== 'undefined') {
if (typeof e.originalEvent[pagePositionProperty] !== 'undefined') {
pageOffset = e.originalEvent[pagePositionProperty];
} else if (e.originalEvent.touches && e.originalEvent.touches[0] &&
typeof e.originalEvent.touches[0][pagePositionProperty] !== 'undefined') {
pageOffset = e.originalEvent.touches[0][pagePositionProperty];
}
} else if (e.touches && e.touches[0] && typeof e.touches[0][pagePositionProperty] !== 'undefined') {
pageOffset = e.touches[0][pagePositionProperty];
} else if (e.currentPoint && (typeof e.currentPoint.x !== 'undefined' || typeof e.currentPoint.y !== 'undefined')) {
pageOffset = this.vertical ? e.currentPoint.y : e.currentPoint.x;
}
if (this.vertical) {
pageOffset -= window.pageYOffset;
}
return this.vertical ? rangeSize - pageOffset : pageOffset - rangeSize;
}
_getPositionFromValue(value) {
const percentage = (value - this.min) / (this.max - this.min);
const pos = percentage * this.maxHandleX;
return isNaN(pos) ? 0 : pos;
}
_getValueFromPosition(pos) {
const percentage = ((pos) / (this.maxHandleX || 1));
const value = this.step * Math.round(percentage * (this.max - this.min) / this.step) + this.min;
return Number((value).toFixed(this.toFixed));
}
_setValue(value, force) {
if (value === this.value && !force) {
return;
}
// Set the new value and fire the `input` event
this.element.value = value;
this.value = value;
dom.triggerEvent(this.element, 'input', { origin: this.identifier });
}
}
RangeSlider.version = VERSION;
RangeSlider.dom = dom;
RangeSlider.functions = func;
RangeSlider.instances = [];
RangeSlider.slidingVertically = false;
================================================
FILE: src/utils/dom.js
================================================
import * as func from './functions';
const EVENT_LISTENER_LIST = 'eventListenerList';
export const detectIE = () => {
const ua = window.navigator.userAgent;
const msie = ua.indexOf('MSIE ');
if (msie > 0) {
return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
}
const trident = ua.indexOf('Trident/');
if (trident > 0) {
const rv = ua.indexOf('rv:');
return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
}
const edge = ua.indexOf('Edge/');
if (edge > 0) {
return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
}
return false;
};
const ieVersion = detectIE();
const eventCaptureParams = window.PointerEvent && !ieVersion ? {passive: false} : false;
/**
* Check if a `element` is visible in the DOM
*
* @param {Element} element
* @return {Boolean}
*/
export const isHidden = (element) => (
element.offsetWidth === 0 || element.offsetHeight === 0 || element.open === false
);
/**
* Get hidden parentNodes of an `element`
*
* @param {Element} element
* @return {Element[]}
*/
export const getHiddenParentNodes = (element) => {
const parents = [];
let node = element.parentNode;
while (node && isHidden(node)) {
parents.push(node);
node = node.parentNode;
}
return parents;
};
/**
* Returns dimensions for an element even if it is not visible in the DOM.
*
* @param {Element} element
* @param {string} key (e.g. offsetWidth …)
* @return {Number}
*/
export const getDimension = (element, key) => {
const hiddenParentNodes = getHiddenParentNodes(element);
const hiddenParentNodesLength = hiddenParentNodes.length;
const hiddenParentNodesStyle = [];
let dimension = element[key];
// Used for native `<details>` elements
const toggleOpenProperty = (element) => {
if (typeof element.open !== 'undefined') {
element.open = !element.open;
}
};
if (hiddenParentNodesLength) {
for (let i = 0; i < hiddenParentNodesLength; i++) {
// Cache the styles to restore then later.
hiddenParentNodesStyle.push({
display: hiddenParentNodes[i].style.display,
height: hiddenParentNodes[i].style.height,
overflow: hiddenParentNodes[i].style.overflow,
visibility: hiddenParentNodes[i].style.visibility
});
hiddenParentNodes[i].style.display = 'block';
hiddenParentNodes[i].style.height = '0';
hiddenParentNodes[i].style.overflow = 'hidden';
hiddenParentNodes[i].style.visibility = 'hidden';
toggleOpenProperty(hiddenParentNodes[i]);
}
dimension = element[key];
for (let j = 0; j < hiddenParentNodesLength; j++) {
toggleOpenProperty(hiddenParentNodes[j]);
hiddenParentNodes[j].style.display = hiddenParentNodesStyle[j].display;
hiddenParentNodes[j].style.height = hiddenParentNodesStyle[j].height;
hiddenParentNodes[j].style.overflow = hiddenParentNodesStyle[j].overflow;
hiddenParentNodes[j].style.visibility = hiddenParentNodesStyle[j].visibility;
}
}
return dimension;
};
/**
*
* @param {HTMLElement} el
* @param {Object} cssObj
* @returns {*}
*/
export const setCss = (el, cssObj) => {
for (const key in cssObj) {
el.style[key] = cssObj[key];
}
return el.style;
};
/**
*
* @param {HTMLElement} elem
* @param {string} className
*/
export const hasClass = (elem, className) => new RegExp(' ' + className + ' ').test(' ' + elem.className + ' ');
/**
*
* @param {HTMLElement} elem
* @param {string} className
*/
export const addClass = (elem, className) => {
if (!hasClass(elem, className)) {
elem.className += ' ' + className;
}
};
/**
*
* @param {HTMLElement} elem
* @param {string} className
*/
export const removeClass = (elem, className) => {
let newClass = ' ' + elem.className.replace(/[\t\r\n]/g, ' ') + ' ';
if (hasClass(elem, className)) {
while (newClass.indexOf(' ' + className + ' ') >= 0) {
newClass = newClass.replace(' ' + className + ' ', ' ');
}
elem.className = newClass.replace(/^\s+|\s+$/g, '');
}
};
/**
*
* @param {HTMLElement} el
* @param {Function} callback
* @param {boolean} andForElement - apply callback for el
* @returns {HTMLElement}
*/
export const forEachAncestors = (el, callback, andForElement) => {
if (andForElement) {
callback(el);
}
while (el.parentNode && !callback(el)) {
el = el.parentNode;
}
return el;
};
/**
*
* @param {HTMLElement} el
* @param {string} name event name
* @param {Object} data
*/
export const triggerEvent = (el, name, data) => {
if (!func.isString(name)) {
throw new TypeError('event name must be String');
}
if (!(el instanceof HTMLElement)) {
throw new TypeError('element must be HTMLElement');
}
name = name.trim();
const event = document.createEvent('CustomEvent');
event.initCustomEvent(name, false, false, data);
el.dispatchEvent(event);
};
/**
* @param {Object} referenceNode after this
* @param {Object} newNode insert this
*/
export const insertAfter = (referenceNode, newNode) =>
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
/**
* Add event listeners and push them to el[EVENT_LISTENER_LIST]
* @param {HTMLElement|Node|Document} el DOM element
* @param {Array} events
* @param {Function} listener
*/
export const addEventListeners = (el, events, listener) => {
events.forEach((eventName) => {
if (!el[EVENT_LISTENER_LIST]) {
el[EVENT_LISTENER_LIST] = {};
}
if (!el[EVENT_LISTENER_LIST][eventName]) {
el[EVENT_LISTENER_LIST][eventName] = [];
}
el.addEventListener(
eventName,
listener,
eventCaptureParams
);
if (el[EVENT_LISTENER_LIST][eventName].indexOf(listener) < 0) {
el[EVENT_LISTENER_LIST][eventName].push(listener);
}
});
};
/**
* Remove event listeners and remove them from el[EVENT_LISTENER_LIST]
* @param {HTMLElement} el DOM element
* @param {Array} events
* @param {Function} listener
*/
export const removeEventListeners = (el, events, listener) => {
events.forEach((eventName) => {
let index;
el.removeEventListener(
eventName,
listener,
eventCaptureParams
);
if (el[EVENT_LISTENER_LIST] && el[EVENT_LISTENER_LIST][eventName] &&
(index = el[EVENT_LISTENER_LIST][eventName].indexOf(listener)) > -1
) {
el[EVENT_LISTENER_LIST][eventName].splice(index, 1);
}
});
};
/**
* Remove ALL event listeners which exists in el[EVENT_LISTENER_LIST]
* @param {RangeSlider} instance
* @param {HTMLElement} el DOM element
*/
export const removeAllListenersFromEl = (instance, el) => {
if (!el[EVENT_LISTENER_LIST]) {
return;
}
/* jshint ignore:start */
/**
*
* @callback listener
* @this {Object} event name
*/
function rm(listener) {
if (listener === instance._startEventListener) {
this.el.removeEventListener(this.eventName, listener, false);
}
}
for (const eventName in el[EVENT_LISTENER_LIST]) {
el[EVENT_LISTENER_LIST][eventName].forEach(rm, {eventName: eventName, el: el});
}
el[EVENT_LISTENER_LIST] = {};
/* jshint ignore:end */
};
/**
* Range feature detection
* @return {Boolean}
*/
export const supportsRange = () => {
const input = document.createElement('input');
input.setAttribute('type', 'range');
return input.type !== 'text';
};
================================================
FILE: src/utils/functions.js
================================================
/**
* Create a random uuid
*/
export const uuid = () => {
const s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
};
/**
* Delays a function for the given number of milliseconds, and then calls
* it with the arguments supplied.
*
* @param {Function} fn function
* @param {Number} wait delay
* @param {Number} args arguments
* @return {Function}
*/
export const delay = (fn, wait, ...args) => setTimeout(() => fn.apply(null, args), wait);
/**
* Returns a debounced function that will make sure the given
* function is not triggered too much.
*
* @param {Function} fn Function to debounce.
* @param {Number} debounceDuration OPTIONAL. The amount of time in milliseconds for which we will debounce the
* function. (defaults to 100ms)
* @return {Function}
*/
export const debounce = (fn, debounceDuration = 100) => (...args) => {
if (!fn.debouncing) {
fn.lastReturnVal = fn.apply(window, args);
fn.debouncing = true;
}
clearTimeout(fn.debounceTimeout);
fn.debounceTimeout = setTimeout(() => {
fn.debouncing = false;
}, debounceDuration);
return fn.lastReturnVal;
};
export const isString = obj => obj === '' + obj;
export const isArray = obj => Object.prototype.toString.call(obj) === '[object Array]';
export const isNumberLike = (obj) =>
(obj !== null && obj !== undefined && ((isString(obj) && isFinite(parseFloat(obj))) || (isFinite(obj))));
export const getFirstNumberLike = (...args) => {
if (!args.length) {
return null;
}
for (let i = 0, len = args.length; i < len; i++) {
if (isNumberLike(args[i])) {
return args[i];
}
}
return null;
};
export const isObject = (obj) => Object.prototype.toString.call(obj) === '[object Object]';
export const simpleExtend = (defaultOpt, options) => {
const opt = {};
for (let key in defaultOpt) {
opt[key] = defaultOpt[key];
}
for (let key in options) {
opt[key] = options[key];
}
return opt;
};
export const between = (pos, min, max) => {
if (pos < min) {
return min;
}
if (pos > max) {
return max;
}
return pos;
};
================================================
FILE: vite.config.js
================================================
import { defineConfig } from 'vite'
import { resolve } from 'path'
import pkg from './package.json'
export default defineConfig({
define: {
VERSION: JSON.stringify(pkg.version),
},
build: {
lib: {
entry: resolve(__dirname, 'src/range-slider.js'),
name: 'rangeSlider',
formats: ['umd', 'es'],
fileName: (format) => format === 'es' ? 'range-slider.esm.js' : 'range-slider.js',
cssFileName: 'range-slider',
},
sourcemap: true,
cssCodeSplit: false,
},
server: {
port: 8000,
open: '/example/index-esm.html',
},
})
gitextract_68uuadx6/ ├── .eslintrc.json ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ └── bug_report.md │ └── workflows/ │ └── publish-from-tag.yaml ├── .gitignore ├── .jshintrc ├── .npmignore ├── LICENSE.md ├── README.md ├── bower.json ├── example/ │ ├── index-esm.html │ ├── index-umd.html │ └── range-slider-flat.css ├── package.json ├── postcss.config.js ├── src/ │ ├── range-slider.css │ ├── range-slider.js │ └── utils/ │ ├── dom.js │ └── functions.js └── vite.config.js
SYMBOL INDEX (30 symbols across 2 files)
FILE: src/range-slider.js
constant MAX_SET_BY_DEFAULT (line 6) | const MAX_SET_BY_DEFAULT = 100;
constant HANDLE_RESIZE_DELAY (line 7) | const HANDLE_RESIZE_DELAY = 300;
constant HANDLE_RESIZE_DEBOUNCE (line 8) | const HANDLE_RESIZE_DEBOUNCE = 50;
class RangeSlider (line 40) | class RangeSlider {
method constructor (line 41) | constructor(element, options) {
method create (line 209) | static create(el, options) {
method _touchMoveScrollHandler (line 229) | static _touchMoveScrollHandler (event) {
method update (line 242) | update(obj, triggerEvents) {
method destroy (line 277) | destroy() {
method _toFixed (line 299) | _toFixed(step) {
method _init (line 303) | _init() {
method _updatePercentFromValue (line 310) | _updatePercentFromValue() {
method _startEventListener (line 319) | _startEventListener(ev, data) {
method _changeEventListener (line 338) | _changeEventListener(ev, data) {
method _update (line 349) | _update(triggerEvent) {
method _addVerticalSlideScrollFix (line 375) | _addVerticalSlideScrollFix() {
method _removeVerticalSlideScrollFix (line 382) | _removeVerticalSlideScrollFix() {
method _handleResize (line 387) | _handleResize() {
method _handleDown (line 396) | _handleDown(e) {
method _handleMove (line 422) | _handleMove(e) {
method _handleEnd (line 430) | _handleEnd(e) {
method _setPosition (line 451) | _setPosition(pos) {
method _setBufferPosition (line 510) | _setBufferPosition(pos) {
method _getPositionFromNode (line 567) | _getPositionFromNode(node) {
method _getRelativePosition (line 582) | _getRelativePosition(e) {
method _getPositionFromValue (line 613) | _getPositionFromValue(value) {
method _getValueFromPosition (line 620) | _getValueFromPosition(pos) {
method _setValue (line 627) | _setValue(value, force) {
FILE: src/utils/dom.js
constant EVENT_LISTENER_LIST (line 3) | const EVENT_LISTENER_LIST = 'eventListenerList';
function rm (line 270) | function rm(listener) {
Condensed preview — 19 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (67K chars).
[
{
"path": ".eslintrc.json",
"chars": 300,
"preview": "{\n \"extends\": \"standard\",\n \"rules\": {\n \"semi\": 0,\n \"no-undef\": 0,\n \"operator-linebreak\": 0,\n "
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 913,
"preview": "---\nname: Bug report\nabout: Create bug report\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**Example**\nProvide link to exam"
},
{
"path": ".github/workflows/publish-from-tag.yaml",
"chars": 421,
"preview": "name: Publish Package\n\non:\n push:\n tags:\n - '*'\n\npermissions:\n id-token: write # Required for OIDC\n contents"
},
{
"path": ".gitignore",
"chars": 55,
"preview": "node_modules\n.idea/\n.directory\n.history/\n.vscode/\ndist/"
},
{
"path": ".jshintrc",
"chars": 412,
"preview": "{\n \"node\": true,\n \"browser\": true,\n \"esnext\": true,\n \"bitwise\": true,\n \"camelcase\": true,\n \"curly\": true,\n \"eqeqe"
},
{
"path": ".npmignore",
"chars": 137,
"preview": ".eslintrc.json\n.git\n.github\n.gitignore\n.history\n.jshintrc\n.npmignore\nbower.json\nexample\nnode_modules\npostcss.config.js\ns"
},
{
"path": "LICENSE.md",
"chars": 1074,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2015 Serhii\n\nPermission is hereby granted, free of charge, to any person obtaining "
},
{
"path": "README.md",
"chars": 2935,
"preview": "# rangeSlider\n\n> Simple, small and fast vanilla JavaScript polyfill for the HTML5 `<input type=\"range\">` slider element."
},
{
"path": "bower.json",
"chars": 804,
"preview": "{\n \"name\": \"rangeslider-pure\",\n \"version\": \"0.5.0\",\n \"homepage\": \"https://github.com/Stryzhevskyi/rangeSlider\",\n \"au"
},
{
"path": "example/index-esm.html",
"chars": 9681,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset=\"utf-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n <title"
},
{
"path": "example/index-umd.html",
"chars": 9742,
"preview": "<!DOCTYPE html>\n<html>\n\n<head>\n <meta charset=\"utf-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n <title"
},
{
"path": "example/range-slider-flat.css",
"chars": 3372,
"preview": ".rangeSlider,\n.rangeSlider__fill {\n background: #7f8c8d;\n display: block;\n height: 8px;\n width: 100%;\n -w"
},
{
"path": "package.json",
"chars": 1592,
"preview": "{\n \"name\": \"rangeslider-pure\",\n \"title\": \"range-slider\",\n \"description\": \"Simple, small and fast vanilla JavaScript p"
},
{
"path": "postcss.config.js",
"chars": 77,
"preview": "export default {\n plugins: {\n autoprefixer: {},\n cssnano: {},\n },\n};\n"
},
{
"path": "src/range-slider.css",
"chars": 1774,
"preview": ".rangeSlider, .rangeSlider__fill {\n display: block;\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3);\n border-ra"
},
{
"path": "src/range-slider.js",
"chars": 19153,
"preview": "import * as dom from './utils/dom';\nimport * as func from './utils/functions';\nimport './range-slider.css';\n\nconst newLi"
},
{
"path": "src/utils/dom.js",
"chars": 7369,
"preview": "import * as func from './functions';\n\nconst EVENT_LISTENER_LIST = 'eventListenerList';\n\nexport const detectIE = () => {\n"
},
{
"path": "src/utils/functions.js",
"chars": 2219,
"preview": "/**\n * Create a random uuid\n */\nexport const uuid = () => {\n const s4 = () => Math.floor((1 + Math.random()) * 0x10000)"
},
{
"path": "vite.config.js",
"chars": 580,
"preview": "import { defineConfig } from 'vite'\nimport { resolve } from 'path'\nimport pkg from './package.json'\n\nexport default defi"
}
]
About this extraction
This page contains the full source code of the Stryzhevskyi/rangeSlider GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 19 files (61.1 KB), approximately 17.5k tokens, and a symbol index with 30 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.