├── .circleci └── config.yml ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── assets ├── logo-horizontal.svg ├── logo.svg └── screenshot-1.png ├── babel.config.js ├── build ├── themes.rollup.config.js ├── vue2.rollup.config.js └── vue3.rollup.config.js ├── dist ├── slider.global.js ├── slider.js ├── slider.vue2.global.js └── slider.vue2.js ├── jest ├── jest.config.vue2.js └── jest.config.vue3.js ├── package.json ├── postcss.config.js ├── src ├── Slider.d.ts ├── Slider.vue ├── composables │ ├── useClasses.js │ ├── useSlider.js │ ├── useTooltip.js │ └── useValue.js ├── index.d.ts └── utils │ ├── arraysEqual.js │ └── isNullish.js ├── tailwind.js ├── tests └── unit │ ├── Slider.spec.js │ ├── composables │ ├── useClasses.spec.js │ ├── useSlider.spec.js │ ├── useTooltip.spec.js │ └── useValue.spec.js │ └── helpers │ ├── vue2.js │ └── vue3.js └── themes ├── default.css ├── default.scss ├── tailwind.css └── tailwind.scss /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | codecov: codecov/codecov@1.1.3 5 | 6 | jobs: 7 | build: 8 | docker: 9 | - image: cimg/node:15.4.0 10 | 11 | steps: 12 | - checkout 13 | 14 | # Download and cache dependencies 15 | - restore_cache: 16 | keys: 17 | - v2-dependencies-{{ checksum "package.json" }} 18 | # fallback to using the latest cache if no exact match is found 19 | - v2-dependencies- 20 | 21 | - run: npm install --legacy-peer-deps 22 | 23 | - save_cache: 24 | paths: 25 | - node_modules 26 | key: v2-dependencies-{{ checksum "package.json" }} 27 | 28 | - run: npm run test:vue3 29 | 30 | - codecov/upload: 31 | file: './coverage/coverage-final.json' 32 | flags: 'vue3' 33 | 34 | - run: npm run test:vue2 35 | 36 | - codecov/upload: 37 | file: './coverage/coverage-final.json' 38 | flags: 'vue2' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .DS_Store 3 | node_modules/ 4 | npm-debug.log 5 | stats.json 6 | .npmrc 7 | coverage/ 8 | package-lock.json -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .editorconfig 3 | .DS_Store 4 | npm-debug.log 5 | node_modules/ 6 | .circleci 7 | assets/ 8 | build/ 9 | coverage/ 10 | jest/ 11 | node_modules/ 12 | tests/ 13 | .gitignore 14 | babel.config.js 15 | CODE_OF_CONDUCT.md 16 | package-lock.json -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v2.1.10 2 | 3 | > `2023-12-12` 4 | 5 | ### 🐞 Bug Fixes 6 | - Export definitions. 7 | 8 | ## v2.1.9 9 | 10 | > `2023-10-14` 11 | 12 | ### 🐞 Bug Fixes 13 | - Type fixes. 14 | 15 | ## v2.1.8 16 | 17 | > `2023-10-14` 18 | 19 | ### 🐞 Bug Fixes 20 | - Type fixes. 21 | 22 | ## v2.1.7 23 | 24 | > `2023-01-13` 25 | 26 | ### 🐞 Bug Fixes 27 | - Removed Slider export from index.d.ts #71. 28 | 29 | ## v2.1.6 30 | 31 | > `2022-12-20` 32 | 33 | ### 🎉 Feature 34 | - Allow Slider instance without `v-model` #54. 35 | - Added new events: `@start`, `@end`, `@drag`, `@slide`, `@set` #68. 36 | 37 | ### 🐞 Bug Fixes 38 | - Not updating correctly when lazy is `false` if dragged below `0` #60. 39 | - Conflict with Tailwind CSS disabled prop resolved #67. 40 | 41 | ## v2.1.5 42 | 43 | > `2022-09-26` 44 | 45 | ### 🎉 Feature 46 | - Unnecessary ES6 feature removed. 47 | 48 | ## v2.1.4 49 | 50 | > `2022-09-26` 51 | 52 | ### 🎉 Features 53 | - A11y improvements. 54 | 55 | ## v2.1.3 56 | 57 | > `2022-09-23` 58 | 59 | ### 🎉 Features 60 | - A11y improvements. 61 | - Added `ariaLabelledby` prop. 62 | 63 | ## v2.1.2 64 | 65 | > `2022-07-22` 66 | 67 | ### 🐞 Bug Fixes 68 | - Fix for `tailwind.css`. 69 | 70 | ## v2.1.1 71 | 72 | > `2022-07-11` 73 | 74 | ### 🎉 Features 75 | - Vue.js `2.7` compatibility 76 | 77 | ## v2.1.0 78 | 79 | > `2022-07-11` 80 | 81 | ### 🎉 Features 82 | - Vue.js `2.7` compatibility 83 | 84 | ## v2.0.10 85 | 86 | > `2022-05-11` 87 | 88 | ### 🎉 Features 89 | - Refresh slider when the number of handles change. 90 | 91 | ## v2.0.9 92 | 93 | > `2022-02-26` 94 | 95 | ### 🎉 Features 96 | - Added support for `array` classes. 97 | 98 | ## v2.0.8 99 | 100 | > `2021-12-09` 101 | 102 | ### 🎉 Features 103 | - Added `lazy` option that prevents updating `v-model` on dragging by default [#32](https://github.com/vueform/slider/issues/32) [#29](https://github.com/vueform/slider/issues/29) [#28](https://github.com/vueform/slider/issues/28). 104 | - Added `tailwind.css`. 105 | 106 | ### 🐞 Bug Fixes 107 | - Wrapped negative multipliers in brackets [#37](https://github.com/vueform/slider/issues/37). 108 | 109 | ## v2.0.7 110 | 111 | > `2021-11-23` 112 | 113 | ### 🐞 Bug Fixes 114 | - Resolved `devDependencies` conflict. 115 | 116 | ## v2.0.6 117 | 118 | > `2021-11-23` 119 | 120 | ### 🎉 Features 121 | - Followed up `nouislider` CSS changes. 122 | 123 | ## v2.0.5 124 | 125 | > `2021-09-07` 126 | 127 | ### 🎉 Features 128 | - **BREAKING**: updated `default.css` classes and `classes` object in general. Please update to the latest, extended version. 129 | - Updated to latest `nouislider`. 130 | - Added `tooltipPosition` prop [#23](https://github.com/vueform/slider/issues/23). 131 | - Added `options` prop. 132 | 133 | ### 🐞 Bug Fixes 134 | - Update `classList` on `classes` change. 135 | 136 | ## v2.0.4 137 | 138 | > `2021-08-02` 139 | 140 | ### 🐞 Bug Fixes 141 | - index.d.ts import fix. 142 | 143 | ## v2.0.3 144 | 145 | > `2021-06-28` 146 | 147 | ### 🐞 Bug Fixes 148 | - Tailwind plugin fix. 149 | 150 | ## v2.0.2 151 | 152 | > `2021-06-27` 153 | 154 | ### 🐞 Bug Fixes 155 | - Merged to main branch. 156 | 157 | ## v2.0.1 158 | 159 | > `2021-06-27` 160 | 161 | ### 🐞 Bug Fixes 162 | - Added missing files. 163 | 164 | ## v2.0.0 165 | 166 | > `2021-06-27` 167 | 168 | ### 🎉 Features 169 | - **BREAKING**: removed `height` prop & rewritten `default.css`. 170 | - Added `classes` prop. 171 | - Added CSS vars. 172 | - `id` no longer defaults to `undefined` instead of `slider`. 173 | 174 | ## v1.0.5 175 | 176 | > `2021-04-09` 177 | 178 | ### 🐞 Bug Fixes 179 | - Fixed `:disabled` property ([#9](https://github.com/vueform/slider/issues/9)). 180 | 181 | ## v1.0.4 182 | 183 | > `2021-04-09` 184 | 185 | ### 🐞 Bug Fixes 186 | - Fixed `:disabled` property ([#9](https://github.com/vueform/slider/issues/9)). 187 | - Don't emit change on exogenous change, fix for [#4](https://github.com/vueform/slider/issues/4). 188 | 189 | ## v1.0.3 190 | 191 | > `2021-01-06` 192 | 193 | ### 🎉 Features 194 | - Added `disabled` & `id` properties 195 | 196 | ## v1.0.2 197 | 198 | > `2021-01-04` 199 | 200 | ### 🐞 Bug Fixes 201 | - `package.json` `browser` property fix 202 | 203 | ## v1.0.1 204 | 205 | > `2021-01-04` 206 | 207 | ### 🐞 Bug Fixes 208 | - Example link fixes 209 | 210 | ## v1.0.0 211 | 212 | > `2021-01-04` 213 | 214 | ### 🎉 Features 215 | - First release -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at adam@vueform.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Adam Berecz . 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | npm 4 | 5 | 6 | CircleCI 7 | 8 | 9 | 10 | 11 | 12 | 13 | npm bundle size (scoped version) 14 | 15 | 16 | 17 | Discord 18 | 19 | 20 | 21 | npm 22 | 23 | 24 |

Vue 3 Slider

25 | 26 | 27 |
28 | 29 |
30 |
31 | 32 |
33 |
34 | 35 | 36 | 37 | 38 |
39 |
40 | 41 | ## Sponsors 42 | 43 |

44 | 45 |
46 | 47 | ## About Vueform 48 | 49 | 50 | Vueform 51 | 52 | 53 |
54 |
55 | 56 | Vueform is comprehensive **form development framework** for Vue.js. It supercharges and standardizes the entire form building process and takes care of everything from rendering to validation and processing. With our latest tool, the **Drag and Drop Form Builder**, you can allow your developers & non-tech workforce to build the most complex forms without coding. 57 | 58 | Feature highlights: 59 | - integrate Vueform **Drag and Drop Form Builder** into **any application** 60 | - save forms in **database** as a JSON 61 | - use your **own form elements** with **custom configuration** options 62 | - a complete theming and templating system with **Tailwind support** 63 | - 25+ form elements with **multi-file uploads**, date pickers and rich text editor 64 | - element **nesting** and **repeating** 65 | - **50+ validators** with async, dependent and custom rules 66 | - **conditional logic** on element & form level 67 | - breaking forms into **steps** with **form wizard** 68 | - **translating** form content and global i18n support. 69 | 70 | 71 | Vueform Builder 72 | 73 |
74 |
75 | 76 | **Learn more:** 77 | - Builder: [https://builder.vueform.com](https://builder.vueform.com?cid=slider) 78 | - Framework: [https://vueform.com](https://vueform.com?cid=slider) 79 | 80 | ## Other libraries 81 | 82 | * [@vueform/multiselect](https://github.com/vueform/multiselect) - Vue 3 multiselect component with single select, multiselect and tagging options. 83 | * [@vueform/toggle](https://github.com/vueform/toggle) - Vue 3 toggle component with labels, custom slots and styling options. 84 | 85 | ## Slider features 86 | 87 | * Vue 2 & 3 support 88 | * 100% coverage 89 | * TypeScript support 90 | * ESM support 91 | * Fully configurable 92 | * Single slider 93 | * Multiple sliders 94 | * Tooltips 95 | * Formatting 96 | * CSS vars support 97 | * Accessibility support 98 | * Tailwind & utility class support 99 | * Based on [noUiSlider](https://github.com/leongersen/noUiSlider) 100 | 101 | ## Sections 102 | 103 | * [Demo](#demo) 104 | * [Installation](#installation) 105 | * [Using with Vue 3](#using-with-vue-3) 106 | * [Using with Vue 2](#using-with-vue-2) 107 | * [Support](#support) 108 | * [Configuration](#configuration) 109 | * [Basic props](#basic-props) 110 | * [Events](#events) 111 | * [Styling](#styling) 112 | * [Styling with CSS vars](#styling-with-css-vars) 113 | * [Styling with Tailwind CSS](#styling-with-tailwind-css) 114 | * [Examples](#examples) 115 | * [Single slider](#single-slider) 116 | * [Multiple slider](#multiple-slider) 117 | * [Tooltip formatting](#tooltip-formatting) 118 | * [Tooltip merging](#tooltip-merging) 119 | * [License](#license) 120 | 121 | ## Demo 122 | 123 | Check out our [demo](https://jsfiddle.net/0Lp1bqyv/). 124 | 125 | ## Installation 126 | 127 | ``` 128 | npm install @vueform/slider 129 | ``` 130 | 131 | ### Using with Vue 3 132 | 133 | ``` vue 134 | 139 | 140 | 154 | 155 | 156 | ``` 157 | 158 | ### Using with Vue 2 159 | 160 | ``` vue 161 | 166 | 167 | 181 | 182 | 183 | ``` 184 | 185 | #### Using with < Vue 2.7 186 | 187 | Switch to [`<= 2.0.10`](https://github.com/vueform/slider/tree/2.0.10) to use the Slider with Vue.js `< 2.7`. 188 | 189 | ## Support 190 | 191 | Join our [Discord channel](https://discord.gg/WhX2nG6GTQ) or [open an issue](https://github.com/vueform/slider/issues). 192 | 193 | ## Configuration 194 | 195 | ### Basic props 196 | 197 | | Name | Type | Default | Description | 198 | | --- | --- | --- | --- | 199 | | **id** | `string` | `slider` | The `id` attribute of slider container DOM. | 200 | | **lazy** | `boolean` | `true` | Whether to update `v-model` only when the slider value is set and not while dragging. If disabled you must not use inline objects as props (eg. `format`, `options`, `classes`) but outsource them to a data property. | 201 | | **disabled** | `boolean` | `false` | Whether the slider should be disabled. | 202 | | **min** | `number` | `0` | Minimum value of the slider. | 203 | | **max** | `number` | `100` | Maximum value of the slider. | 204 | | **step** | `number` | `1` | The jump between intervals. If `-1` it enables fractions (eg. `1.23`). | 205 | | **tooltips** | `boolean` | `true` | Whether tooltips should show above handlers. | 206 | | **showTooltip** | `string` | `'always'` | When tooltips should be shown. Possible values: `always\|focus\|drag`. | 207 | | **merge** | `number` | `-1` | The step distance between two handles when their tooltips should be merged (when `step` is `-1` then `1` is assumed). Eg:

`{ merge: 5, step: 10 }`
-> values: `0, <=50` will merge
-> values: `0, 60` will not merge

`{ merge: 5, step: -1 }`
-> values: `0, <=5` will merge
-> values: `0, 5.01` will not merge | 208 | | **format** | `object\|function` | | Formats the tooltip. It can be either a function that receives a `value` param and expects a string or number as return or an object with the following properties:
`prefix` - eg `$` -> `$100`
`suffix` - eg `USD` -> `100USD`
`decimals` - eg `2` -> `100.00`
`thousand` - eg `,` - `1,000` | 209 | | **orientation** | `string` | `'horizontal'` | The orientation of the slider. Possible values: `horizontal\|vertical` | 210 | | **direction** | `string` | `'ltr'` | The direction of the slider. By default value increases *left-to-right* and *top-to-bottom*, which is reversed when using `rtl`. Possible values: `ltr\|rtl` | 211 | | **tooltipPosition** | `string` | `null` | The position of the slider tooltips. Possible values: `null\|'top'\|'bottom'\|'left'\|'right'` depending on `orientation` prop. When `null` it equals to `orientation` default (`'top'` for `'horizontal'` and `'left'` for `'vertical'`). | 212 | | **aria** | `object` | | An object containing aria attributes to be added for each handle. | 213 | | **ariaLabelledby** | `string` | `null` | Sets the `aria-labelledby` attribute of handles. | 214 | | **options** | `object` | `{}` | Additional [options](https://refreshless.com/nouislider/slider-options/) for noUiSlider. | 215 | | **classes** | `object` | | An object of class names that gets merged with the default values. Default:
`{`
  `target: 'slider-target',`
  `ltr: 'slider-ltr',`
  `rtl: 'slider-rtl',`
  `horizontal: 'slider-horizontal',`
  `vertical: 'slider-vertical',`
  `textDirectionRtl: 'slider-txt-dir-rtl',`
  `textDirectionLtr: 'slider-txt-dir-ltr',`
  `base: 'slider-base',`
  `connects: 'slider-connects',`
  `connect: 'slider-connect',`
  `origin: 'slider-origin',`
  `handle: 'slider-handle',`
  `handleLower: 'slider-handle-lower',`
  `handleUpper: 'slider-handle-upper',`
  `touchArea: 'slider-touch-area',`
  `tooltip: 'slider-tooltip',`
  `tooltipTop: 'slider-tooltip-top',`
  `tooltipBottom: 'slider-tooltip-bottom',`
  `tooltipLeft: 'slider-tooltip-left',`
  `tooltipRight: 'slider-tooltip-right',`
  `active: 'slider-active',`
  `draggable: 'slider-draggable',`
  `tap: 'slider-state-tap',`
  `drag: 'slider-state-drag'`
`}` | 216 | 217 | 218 | Vueform 219 | 220 | 221 | ### Events 222 | 223 | | Event | Attributes | Description | 224 | | --- | --- | --- | 225 | | **@change** | `value` | Emitted when dragging the slider is finished or it's value changed by clicking, keyboard or programmatical set. | 226 | | **@update** | `value` | Emitted in the same scenarios as in `@change`, but also when the slider is being dragged if `lazy` option is disabled. | 227 | | **@set** | `value` | Emitted in the same scenarios as in `@change`, but also when the slider's `.set()` method is called. | 228 | | **@slide** | `value` | Emitted while the slider moves. | 229 | | **@drag** | `value` | Emitted the slider connect moves while dragging. | 230 | | **@start** | `value` | Emitted when the handle is activated and dragging started. | 231 | | **@end** | `value` | Emitted when the dragging ended. | 232 | 233 | ## Styling 234 | 235 | ### Styling with CSS vars 236 | 237 | The following CSS variables can be used to customize slider when using `default.css`: 238 | 239 | ``` css 240 | --slider-bg: #D1D5DB; 241 | --slider-connect-bg: #10B981; 242 | --slider-connect-bg-disabled: #9CA3AF; 243 | --slider-height: 6px; 244 | --slider-vertical-height: 300px; 245 | --slider-radius: 9999px; 246 | 247 | --slider-handle-bg: #fff; 248 | --slider-handle-border: 0; 249 | --slider-handle-width: 16px; 250 | --slider-handle-height: 16px; 251 | --slider-handle-radius: 9999px; 252 | --slider-handle-shadow: 0.5px 0.5px 2px 1px rgba(0,0,0,.32); 253 | --slider-handle-shadow-active: 0.5px 0.5px 2px 1px rgba(0,0,0,.42); 254 | --slider-handle-ring-width: 3px; 255 | --slider-handle-ring-color: #10B98130; 256 | 257 | --slider-tooltip-font-size: 0.875rem; 258 | --slider-tooltip-line-height: 1.25rem; 259 | --slider-tooltip-font-weight: 600; 260 | --slider-tooltip-min-width: 20px; 261 | --slider-tooltip-bg: #10B981; 262 | --slider-tooltip-bg-disabled: #9CA3AF; 263 | --slider-tooltip-color: #fff; 264 | --slider-tooltip-radius: 5px; 265 | --slider-tooltip-py: 2px; 266 | --slider-tooltip-px: 6px; 267 | --slider-tooltip-arrow-size: 5px; 268 | --slider-tooltip-distance: 3px; 269 | ``` 270 | 271 | Override them globally: 272 | 273 | ``` css 274 | :root { 275 | --slider-connect-bg: #3B82F6; 276 | --slider-tooltip-bg: #3B82F6; 277 | --slider-handle-ring-color: #3B82F630; 278 | } 279 | ``` 280 | 281 | Or on instance level: 282 | 283 | ``` vue 284 | 288 | 289 | 293 | ``` 294 | 295 | ``` css 296 | .slider-red { 297 | --slider-connect-bg: #EF4444; 298 | --slider-tooltip-bg: #EF4444; 299 | --slider-handle-ring-color: #EF444430; 300 | } 301 | 302 | .slider-blue { 303 | --slider-connect-bg: #3B82F6; 304 | --slider-tooltip-bg: #3B82F6; 305 | --slider-handle-ring-color: #3B82F630; 306 | } 307 | ``` 308 | 309 | ### Styling with Tailwind CSS 310 | 311 | To use the slider with Tailwind CSS you must add it as a plugin to `tailwind.config.js`: 312 | 313 | ``` js 314 | // tailwind.config.js 315 | 316 | module.exports = { 317 | // ... 318 | plugins: [ 319 | require('@vueform/slider/tailwind'), 320 | ] 321 | } 322 | ``` 323 | 324 | This plugin adds certain utilities and variants which are neccessary for the slider but Tailwind does not provide by default. 325 | 326 | After that you need to import `themes/tailwind.scss` to you main component: 327 | 328 | ``` vue 329 | 334 | 335 | 338 | 339 | 342 | ``` 343 | 344 | #### Using `:classes` prop 345 | 346 | Alternatively you can define class names directly by passing them to the `Slider` component via `classes` property. When using this approach you don't need to import `tailwind.scss`. Here's a default styling for Tailwind CSS (the same included in `tailwind.scss`): 347 | 348 | ``` vue 349 | 379 | ``` 380 | 381 | There are certain variants that help detecting different states/config of the slider: 382 | * `h` - applied when the slider is horizontal 383 | * `v` - applied when the slider is vertical 384 | * `merge-h` - applied when the slider is horizontal and tooltips are merged 385 | * `merge-v` - applied when the slider is horizontal and tooltips are merged 386 | * `disabled` - applied when the slider is disabled 387 | * `txt-rtl-h` - applied when the slider is horizontal and text direction is set to `rtl` 388 | * `tap` - applied when the slider bar is being taped to jump to certain position 389 | * `tt-focus` - applied when the slider should only display tooltips on focus (`showToolip: 'focus'`) and the slider is not focused 390 | * `tt-focused` - applied when the slider should only display tooltips on focus and the slider is focused 391 | * `tt-drag` - applied when the slider should only display tooltips on drag (`showToolip: 'drag'`) and the slider is not being dragged 392 | * `tt-dragging` - applied when the slider should only display tooltips on drag and the slider is being dragged 393 | 394 | The `target` class receives `ltr`, `rtl`, `horizontal`, `vertical`, `textDirectionRtl`, `textDirectionLtr`, `focused`, `tooltipFocus`, `tooltipDrag`, `tap`, and `drag` classes when the related state is applied. 395 | 396 | Certain classes do not define any styles (like `.slider-horizontal`, `.slider-vertical`) but only required to detect certain states. If you are changing the class list for any class name make sure to always keep the ones that start with `slider-` to be able to use the utilities mentioned above (`h`, `v`, etc). 397 | 398 | In case you need to override the same type of utility you might use [@neojp/tailwind-important-variant](https://www.npmjs.com/package/@neojp/tailwindcss-important-variant) and use eg. `bg-green-500!`. 399 | 400 | ## Examples 401 | 402 | * [Single slider](#single-slider) 403 | * [Multiple slider](#multiple-slider) 404 | * [Tooltip formatting](#tooltip-formatting) 405 | * [Tooltip mergin](#tooltip-merging) 406 | * [Vertical slider](#vertical-slider) 407 | 408 | ### Single slider 409 | 410 | ``` vue 411 | 416 | 417 | 427 | ``` 428 | 429 | [JSFiddle - Example #1](https://jsfiddle.net/0Lp1bqyv/) 430 | 431 | ### Multiple slider 432 | 433 | ``` vue 434 | 439 | 440 | 450 | ``` 451 | 452 | [JSFiddle - Example #2](https://jsfiddle.net/0Lp1bqyv/) 453 | 454 | ### Tooltip formatting 455 | 456 | ``` vue 457 | 463 | 464 | 477 | ``` 478 | 479 | [JSFiddle - Example #3](https://jsfiddle.net/0Lp1bqyv/) 480 | 481 | ### Tooltip merging 482 | 483 | ``` vue 484 | 491 | 492 | 507 | ``` 508 | 509 | [JSFiddle - Example #4](https://jsfiddle.net/0Lp1bqyv/) 510 | 511 | ### Vertical slider 512 | 513 | ``` vue 514 | 519 | 520 | 532 | ``` 533 | 534 | [JSFiddle - Example #5](https://jsfiddle.net/0Lp1bqyv/) 535 | 536 | ## License 537 | 538 | [MIT](https://github.com/vueform/slider/blob/main/LICENSE.md) -------------------------------------------------------------------------------- /assets/logo-horizontal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | logo-horizontal 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | logo 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | -------------------------------------------------------------------------------- /assets/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vueform/slider/5a914602a966ec27676603d17f0da1418fede45d/assets/screenshot-1.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'presets': [ 3 | ['@babel/preset-env', { 4 | modules: false 5 | }], 6 | ], 7 | 'env': { 8 | 'test': { 9 | 'presets': [ 10 | [ 11 | '@babel/preset-env', { 12 | 'targets': { 'node': 'current' } 13 | } 14 | ] 15 | ] 16 | } 17 | }, 18 | } -------------------------------------------------------------------------------- /build/themes.rollup.config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | import postcss from 'rollup-plugin-postcss' 4 | import autoprefixer from 'autoprefixer' 5 | 6 | const themes = fs.readdirSync(__dirname + '/../themes').filter(f=>f.indexOf('.scss') !== -1).map(f=>f.replace('.scss','')) 7 | 8 | export default themes.map((theme) => { 9 | return { 10 | input: `themes/${theme}.scss`, 11 | output: { 12 | file: `themes/${theme}.css`, 13 | format: 'esm', 14 | }, 15 | plugins: [ 16 | postcss({ 17 | extract: true, 18 | minimize: theme === 'tailwind' ? false : true, 19 | plugins: [ 20 | autoprefixer(), 21 | ] 22 | }), 23 | ] 24 | } 25 | }) -------------------------------------------------------------------------------- /build/vue2.rollup.config.js: -------------------------------------------------------------------------------- 1 | import vue from 'vue-prev-rollup-plugin-vue' 2 | import babel from '@rollup/plugin-babel' 3 | import commonjs from '@rollup/plugin-commonjs' 4 | import { nodeResolve } from '@rollup/plugin-node-resolve' 5 | import { terser } from 'rollup-plugin-terser' 6 | 7 | export default [ 8 | { 9 | input: 'src/Slider.vue', 10 | output: { 11 | file: 'dist/slider.vue2.js', 12 | format: 'esm', 13 | }, 14 | plugins: [ 15 | vue(), 16 | nodeResolve({ 17 | resolveOnly: ['wnumb', 'nouislider'] 18 | }), 19 | commonjs(), 20 | babel({ 21 | babelHelpers: 'runtime', 22 | skipPreflightCheck: true, 23 | }), 24 | terser(), 25 | ], 26 | external: ['vue'], 27 | }, 28 | { 29 | input: 'src/Slider.vue', 30 | output: { 31 | file: 'dist/slider.vue2.global.js', 32 | format: 'iife', 33 | name: 'VueformSlider', 34 | globals: { 35 | 'vue': 'Vue', 36 | } 37 | }, 38 | plugins: [ 39 | vue(), 40 | nodeResolve({ 41 | resolveOnly: ['wnumb', 'nouislider'] 42 | }), 43 | commonjs(), 44 | babel({ 45 | babelHelpers: 'bundled', 46 | }), 47 | terser() 48 | ], 49 | external: ['vue'], 50 | } 51 | ] -------------------------------------------------------------------------------- /build/vue3.rollup.config.js: -------------------------------------------------------------------------------- 1 | import vue from 'vue-next-rollup-plugin-vue' 2 | import babel from '@rollup/plugin-babel' 3 | import commonjs from '@rollup/plugin-commonjs' 4 | import { nodeResolve } from '@rollup/plugin-node-resolve' 5 | import { terser } from 'rollup-plugin-terser' 6 | 7 | export default [ 8 | { 9 | input: 'src/Slider.vue', 10 | output: { 11 | file: 'dist/slider.js', 12 | format: 'esm', 13 | }, 14 | plugins: [ 15 | vue(), 16 | nodeResolve({ 17 | resolveOnly: ['wnumb', 'nouislider'] 18 | }), 19 | commonjs(), 20 | babel({ 21 | babelHelpers: 'runtime', 22 | skipPreflightCheck: true, 23 | }), 24 | terser(), 25 | ], 26 | external: 'vue', 27 | }, 28 | { 29 | input: 'src/Slider.vue', 30 | output: { 31 | file: 'dist/slider.global.js', 32 | format: 'iife', 33 | name: 'VueformSlider', 34 | globals: { 35 | 'vue': 'Vue', 36 | } 37 | }, 38 | plugins: [ 39 | vue(), 40 | nodeResolve({ 41 | resolveOnly: ['wnumb', 'nouislider'] 42 | }), 43 | commonjs(), 44 | babel({ 45 | babelHelpers: 'bundled', 46 | }), 47 | terser() 48 | ], 49 | external: ['vue'], 50 | } 51 | ] -------------------------------------------------------------------------------- /dist/slider.global.js: -------------------------------------------------------------------------------- 1 | var VueformSlider=function(e){"use strict";function t(e){return-1!==[null,void 0,!1].indexOf(e)}"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self&&self;function r(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function i(e){var t={exports:{}};return e(t,t.exports),t.exports}var n=i((function(e,t){e.exports=function(){var e=["decimals","thousand","mark","prefix","suffix","encoder","decoder","negativeBefore","negative","edit","undo"];function t(e){return e.split("").reverse().join("")}function r(e,t){return e.substring(0,t.length)===t}function i(e,t){return e.slice(-1*t.length)===t}function n(e,t,r){if((e[t]||e[r])&&e[t]===e[r])throw new Error(t)}function o(e){return"number"==typeof e&&isFinite(e)}function a(e,t){return e=e.toString().split("e"),(+((e=(e=Math.round(+(e[0]+"e"+(e[1]?+e[1]+t:t)))).toString().split("e"))[0]+"e"+(e[1]?+e[1]-t:-t))).toFixed(t)}function s(e,r,i,n,s,l,u,c,p,d,f,h){var m,v,g,b=h,y="",S="";return l&&(h=l(h)),!!o(h)&&(!1!==e&&0===parseFloat(h.toFixed(e))&&(h=0),h<0&&(m=!0,h=Math.abs(h)),!1!==e&&(h=a(h,e)),-1!==(h=h.toString()).indexOf(".")?(g=(v=h.split("."))[0],i&&(y=i+v[1])):g=h,r&&(g=t(g).match(/.{1,3}/g),g=t(g.join(t(r)))),m&&c&&(S+=c),n&&(S+=n),m&&p&&(S+=p),S+=g,S+=y,s&&(S+=s),d&&(S=d(S,b)),S)}function l(e,t,n,a,s,l,u,c,p,d,f,h){var m,v="";return f&&(h=f(h)),!(!h||"string"!=typeof h)&&(c&&r(h,c)&&(h=h.replace(c,""),m=!0),a&&r(h,a)&&(h=h.replace(a,"")),p&&r(h,p)&&(h=h.replace(p,""),m=!0),s&&i(h,s)&&(h=h.slice(0,-1*s.length)),t&&(h=h.split(t).join("")),n&&(h=h.replace(n,".")),m&&(v+="-"),""!==(v=(v+=h).replace(/[^0-9\.\-.]/g,""))&&(v=Number(v),u&&(v=u(v)),!!o(v)&&v))}function u(t){var r,i,o,a={};for(void 0===t.suffix&&(t.suffix=t.postfix),r=0;r=0&&o<8))throw new Error(i);a[i]=o}else if("encoder"===i||"decoder"===i||"edit"===i||"undo"===i){if("function"!=typeof o)throw new Error(i);a[i]=o}else{if("string"!=typeof o)throw new Error(i);a[i]=o}return n(a,"mark","thousand"),n(a,"prefix","negative"),n(a,"prefix","negativeBefore"),a}function c(t,r,i){var n,o=[];for(n=0;n0&&(h(e,t),setTimeout((function(){m(e,t)}),r))}function p(e){return Math.max(Math.min(e,100),0)}function d(e){return Array.isArray(e)?e:[e]}function f(e){var t=(e=String(e)).split(".");return t.length>1?t[1].length:0}function h(e,t){e.classList&&!/\s/.test(t)?e.classList.add(t):e.className+=" "+t}function m(e,t){e.classList&&!/\s/.test(t)?e.classList.remove(t):e.className=e.className.replace(new RegExp("(^|\\b)"+t.split(" ").join("|")+"(\\b|$)","gi")," ")}function v(e,t){return e.classList?e.classList.contains(t):new RegExp("\\b"+t+"\\b").test(e.className)}function g(e){var t=void 0!==window.pageXOffset,r="CSS1Compat"===(e.compatMode||"");return{x:t?window.pageXOffset:r?e.documentElement.scrollLeft:e.body.scrollLeft,y:t?window.pageYOffset:r?e.documentElement.scrollTop:e.body.scrollTop}}function b(){return window.navigator.pointerEnabled?{start:"pointerdown",move:"pointermove",end:"pointerup"}:window.navigator.msPointerEnabled?{start:"MSPointerDown",move:"MSPointerMove",end:"MSPointerUp"}:{start:"mousedown touchstart",move:"mousemove touchmove",end:"mouseup touchend"}}function y(){var e=!1;try{var t=Object.defineProperty({},"passive",{get:function(){e=!0}});window.addEventListener("test",null,t)}catch(e){}return e}function S(){return window.CSS&&CSS.supports&&CSS.supports("touch-action","none")}function x(e,t){return 100/(t-e)}function w(e,t,r){return 100*t/(e[r+1]-e[r])}function E(e,t){return w(e,e[0]<0?t+Math.abs(e[0]):t-e[0],0)}function P(e,t){return t*(e[1]-e[0])/100+e[0]}function N(e,t){for(var r=1;e>=t[r];)r+=1;return r}function C(e,t,r){if(r>=e.slice(-1)[0])return 100;var i=N(r,e),n=e[i-1],o=e[i],a=t[i-1],s=t[i];return a+E([n,o],r)/x(a,s)}function k(e,t,r){if(r>=100)return e.slice(-1)[0];var i=N(r,t),n=e[i-1],o=e[i],a=t[i-1];return P([n,o],(r-a)*x(a,t[i]))}function V(e,t,r,i){if(100===i)return i;var n=N(i,e),o=e[n-1],a=e[n];return r?i-o>(a-o)/2?a:o:t[n-1]?e[n-1]+s(i-e[n-1],t[n-1]):i}var A,M;e.PipsMode=void 0,(M=e.PipsMode||(e.PipsMode={})).Range="range",M.Steps="steps",M.Positions="positions",M.Count="count",M.Values="values",e.PipsType=void 0,(A=e.PipsType||(e.PipsType={}))[A.None=-1]="None",A[A.NoValue=0]="NoValue",A[A.LargeValue=1]="LargeValue",A[A.SmallValue=2]="SmallValue";var L=function(){function e(e,t,r){var i;this.xPct=[],this.xVal=[],this.xSteps=[],this.xNumSteps=[],this.xHighestCompleteStep=[],this.xSteps=[r||!1],this.xNumSteps=[!1],this.snap=t;var n=[];for(Object.keys(e).forEach((function(t){n.push([d(e[t]),t])})),n.sort((function(e,t){return e[0][0]-t[0][0]})),i=0;ithis.xPct[n+1];)n++;else e===this.xPct[this.xPct.length-1]&&(n=this.xPct.length-2);r||e!==this.xPct[n+1]||n++,null===t&&(t=[]);var o=1,a=t[n],s=0,l=0,u=0,c=0;for(i=r?(e-this.xPct[n])/(this.xPct[n+1]-this.xPct[n]):(this.xPct[n+1]-e)/(this.xPct[n+1]-this.xPct[n]);a>0;)s=this.xPct[n+1+c]-this.xPct[n+c],t[n+c]*o+100-100*i>100?(l=s*i,o=(a-100*i)/t[n+c],i=1):(l=t[n+c]*s/100*o,o=0),r?(u-=l,this.xPct.length+c>=1&&c--):(u+=l,this.xPct.length-c>=1&&c++),a=t[n+c]*o;return e+u},e.prototype.toStepping=function(e){return e=C(this.xVal,this.xPct,e)},e.prototype.fromStepping=function(e){return k(this.xVal,this.xPct,e)},e.prototype.getStep=function(e){return e=V(this.xPct,this.xSteps,this.snap,e)},e.prototype.getDefaultStep=function(e,t,r){var i=N(e,this.xPct);return(100===e||t&&e===this.xPct[i-1])&&(i=Math.max(i-1,1)),(this.xVal[i]-this.xVal[i-1])/r},e.prototype.getNearbySteps=function(e){var t=N(e,this.xPct);return{stepBefore:{startValue:this.xVal[t-2],step:this.xNumSteps[t-2],highestStep:this.xHighestCompleteStep[t-2]},thisStep:{startValue:this.xVal[t-1],step:this.xNumSteps[t-1],highestStep:this.xHighestCompleteStep[t-1]},stepAfter:{startValue:this.xVal[t],step:this.xNumSteps[t],highestStep:this.xHighestCompleteStep[t]}}},e.prototype.countStepDecimals=function(){var e=this.xNumSteps.map(f);return Math.max.apply(null,e)},e.prototype.hasNoSize=function(){return this.xVal[0]===this.xVal[this.xVal.length-1]},e.prototype.convert=function(e){return this.getStep(this.toStepping(e))},e.prototype.handleEntryPoint=function(e,t){var r;if(!u(r="min"===e?0:"max"===e?100:parseFloat(e))||!u(t[0]))throw new Error("noUiSlider: 'range' value isn't numeric.");this.xPct.push(r),this.xVal.push(t[0]);var i=Number(t[1]);r?this.xSteps.push(!isNaN(i)&&i):isNaN(i)||(this.xSteps[0]=i),this.xHighestCompleteStep.push(0)},e.prototype.handleStepPoint=function(e,t){if(t)if(this.xVal[e]!==this.xVal[e+1]){this.xSteps[e]=w([this.xVal[e],this.xVal[e+1]],t,0)/x(this.xPct[e],this.xPct[e+1]);var r=(this.xVal[e+1]-this.xVal[e])/this.xNumSteps[e],i=Math.ceil(Number(r.toFixed(3))-1),n=this.xVal[e]+this.xNumSteps[e]*i;this.xHighestCompleteStep[e]=n}else this.xSteps[e]=this.xHighestCompleteStep[e]=this.xVal[e]},e}(),U={to:function(e){return void 0===e?"":e.toFixed(2)},from:Number},O={target:"target",base:"base",origin:"origin",handle:"handle",handleLower:"handle-lower",handleUpper:"handle-upper",touchArea:"touch-area",horizontal:"horizontal",vertical:"vertical",background:"background",connect:"connect",connects:"connects",ltr:"ltr",rtl:"rtl",textDirectionLtr:"txt-dir-ltr",textDirectionRtl:"txt-dir-rtl",draggable:"draggable",drag:"state-drag",tap:"state-tap",active:"active",tooltip:"tooltip",pips:"pips",pipsHorizontal:"pips-horizontal",pipsVertical:"pips-vertical",marker:"marker",markerHorizontal:"marker-horizontal",markerVertical:"marker-vertical",markerNormal:"marker-normal",markerLarge:"marker-large",markerSub:"marker-sub",value:"value",valueHorizontal:"value-horizontal",valueVertical:"value-vertical",valueNormal:"value-normal",valueLarge:"value-large",valueSub:"value-sub"},D={tooltips:".__tooltips",aria:".__aria"};function j(e,t){if(!u(t))throw new Error("noUiSlider: 'step' is not numeric.");e.singleStep=t}function F(e,t){if(!u(t))throw new Error("noUiSlider: 'keyboardPageMultiplier' is not numeric.");e.keyboardPageMultiplier=t}function T(e,t){if(!u(t))throw new Error("noUiSlider: 'keyboardMultiplier' is not numeric.");e.keyboardMultiplier=t}function z(e,t){if(!u(t))throw new Error("noUiSlider: 'keyboardDefaultStep' is not numeric.");e.keyboardDefaultStep=t}function H(e,t){if("object"!=typeof t||Array.isArray(t))throw new Error("noUiSlider: 'range' is not an object.");if(void 0===t.min||void 0===t.max)throw new Error("noUiSlider: Missing 'min' or 'max' in 'range'.");e.spectrum=new L(t,e.snap||!1,e.singleStep)}function q(e,t){if(t=d(t),!Array.isArray(t)||!t.length)throw new Error("noUiSlider: 'start' option is incorrect.");e.handles=t.length,e.start=t}function R(e,t){if("boolean"!=typeof t)throw new Error("noUiSlider: 'snap' option must be a boolean.");e.snap=t}function B(e,t){if("boolean"!=typeof t)throw new Error("noUiSlider: 'animate' option must be a boolean.");e.animate=t}function _(e,t){if("number"!=typeof t)throw new Error("noUiSlider: 'animationDuration' option must be a number.");e.animationDuration=t}function $(e,t){var r,i=[!1];if("lower"===t?t=[!0,!1]:"upper"===t&&(t=[!1,!0]),!0===t||!1===t){for(r=1;r1)throw new Error("noUiSlider: 'padding' option must not exceed 100% of the range.")}}function G(e,t){switch(t){case"ltr":e.dir=0;break;case"rtl":e.dir=1;break;default:throw new Error("noUiSlider: 'direction' option was not recognized.")}}function J(e,t){if("string"!=typeof t)throw new Error("noUiSlider: 'behaviour' must be a string containing options.");var r=t.indexOf("tap")>=0,i=t.indexOf("drag")>=0,n=t.indexOf("fixed")>=0,o=t.indexOf("snap")>=0,a=t.indexOf("hover")>=0,s=t.indexOf("unconstrained")>=0,l=t.indexOf("drag-all")>=0,u=t.indexOf("smooth-steps")>=0;if(n){if(2!==e.handles)throw new Error("noUiSlider: 'fixed' behaviour must be used with 2 handles");Y(e,e.start[1]-e.start[0])}if(s&&(e.margin||e.limit))throw new Error("noUiSlider: 'unconstrained' behaviour cannot be used with margin or limit");e.events={tap:r||o,drag:i,dragAll:l,smoothSteps:u,fixed:n,snap:o,hover:a,unconstrained:s}}function K(e,t){if(!1!==t)if(!0===t||r(t)){e.tooltips=[];for(var i=0;i= 2) required for mode 'count'.");for(var r=t.values-1,i=100/r,n=[];r--;)n[r]=r*i;return n.push(100),J(n,t.stepped)}return t.mode===e.PipsMode.Positions?J(t.values,t.stepped):t.mode===e.PipsMode.Values?t.stepped?t.values.map((function(e){return k.fromStepping(k.getStep(k.toStepping(e)))})):t.values:[]}function J(e,t){return e.map((function(e){return k.fromStepping(t?k.getStep(e):e)}))}function K(t){function r(e,t){return Number((e+t).toFixed(7))}var i=G(t),n={},o=k.xVal[0],s=k.xVal[k.xVal.length-1],l=!1,u=!1,c=0;return(i=a(i.slice().sort((function(e,t){return e-t}))))[0]!==o&&(i.unshift(o),l=!0),i[i.length-1]!==s&&(i.push(s),u=!0),i.forEach((function(o,a){var s,p,d,f,h,m,v,g,b,y,S=o,x=i[a+1],w=t.mode===e.PipsMode.Steps;for(w&&(s=k.xNumSteps[a]),s||(s=x-S),void 0===x&&(x=S),s=Math.max(s,1e-7),p=S;p<=x;p=r(p,s)){for(g=(h=(f=k.toStepping(p))-c)/(t.density||1),y=h/(b=Math.round(g)),d=1;d<=b;d+=1)n[(m=c+d*y).toFixed(5)]=[k.fromStepping(m),0];v=i.indexOf(p)>-1?e.PipsType.LargeValue:w?e.PipsType.SmallValue:e.PipsType.NoValue,!a&&l&&p!==x&&(v=0),p===x&&u||(n[f.toFixed(5)]=[p,v]),c=f}})),n}function Q(t,i,n){var o,a,s=O.createElement("div"),l=((o={})[e.PipsType.None]="",o[e.PipsType.NoValue]=r.cssClasses.valueNormal,o[e.PipsType.LargeValue]=r.cssClasses.valueLarge,o[e.PipsType.SmallValue]=r.cssClasses.valueSub,o),u=((a={})[e.PipsType.None]="",a[e.PipsType.NoValue]=r.cssClasses.markerNormal,a[e.PipsType.LargeValue]=r.cssClasses.markerLarge,a[e.PipsType.SmallValue]=r.cssClasses.markerSub,a),c=[r.cssClasses.valueHorizontal,r.cssClasses.valueVertical],p=[r.cssClasses.markerHorizontal,r.cssClasses.markerVertical];function d(e,t){var i=t===r.cssClasses.value,n=i?l:u;return t+" "+(i?c:p)[r.ort]+" "+n[e]}function f(t,o,a){if((a=i?i(o,a):a)!==e.PipsType.None){var l=z(s,!1);l.className=d(a,r.cssClasses.marker),l.style[r.style]=t+"%",a>e.PipsType.NoValue&&((l=z(s,!1)).className=d(a,r.cssClasses.value),l.setAttribute("data-value",String(o)),l.style[r.style]=t+"%",l.innerHTML=String(n.to(o)))}}return h(s,r.cssClasses.pips),h(s,0===r.ort?r.cssClasses.pipsHorizontal:r.cssClasses.pipsVertical),Object.keys(t).forEach((function(e){f(e,t[e][0],t[e][1])})),s}function Z(){w&&(i(w),w=null)}function ee(e){Z();var t=K(e),r=e.filter,i=e.format||{to:function(e){return String(Math.round(e))}};return w=C.appendChild(Q(t,r,i))}function te(){var e=u.getBoundingClientRect(),t="offset"+["Width","Height"][r.ort];return 0===r.ort?e.width||u[t]:e.height||u[t]}function re(e,t,i,n){var o=function(o){var a=ie(o,n.pageOffset,n.target||t);return!!a&&!($()&&!n.doNotReject)&&!(v(C,r.cssClasses.tap)&&!n.doNotReject)&&!(e===P.start&&void 0!==a.buttons&&a.buttons>1)&&(!n.hover||!a.buttons)&&(N||a.preventDefault(),a.calcPoint=a.points[r.ort],void i(a,n))},a=[];return e.split(" ").forEach((function(e){t.addEventListener(e,o,!!N&&{passive:!0}),a.push([e,o])})),a}function ie(e,t,r){var i=0===e.type.indexOf("touch"),n=0===e.type.indexOf("mouse"),o=0===e.type.indexOf("pointer"),a=0,s=0;if(0===e.type.indexOf("MSPointer")&&(o=!0),"mousedown"===e.type&&!e.buttons&&!e.touches)return!1;if(i){var l=function(t){var i=t.target;return i===r||r.contains(i)||e.composed&&e.composedPath().shift()===r};if("touchstart"===e.type){var u=Array.prototype.filter.call(e.touches,l);if(u.length>1)return!1;a=u[0].pageX,s=u[0].pageY}else{var c=Array.prototype.find.call(e.changedTouches,l);if(!c)return!1;a=c.pageX,s=c.pageY}}return t=t||g(O),(n||o)&&(a=e.clientX+t.x,s=e.clientY+t.y),e.pageOffset=t,e.points=[a,s],e.cursor=n||o,e}function ne(e){var t=100*(e-l(u,r.ort))/te();return t=p(t),r.dir?100-t:t}function ae(e){var t=100,r=!1;return f.forEach((function(i,n){if(!X(n)){var o=A[n],a=Math.abs(o-e);(ao||100===a&&100===t)&&(r=n,t=a)}})),r}function se(e,t){"mouseout"===e.type&&"HTML"===e.target.nodeName&&null===e.relatedTarget&&ue(e,t)}function le(e,t){if(-1===navigator.appVersion.indexOf("MSIE 9")&&0===e.buttons&&0!==t.buttonsProperty)return ue(e,t);var i=(r.dir?-1:1)*(e.calcPoint-t.startCalcPoint);xe(i>0,100*i/t.baseSize,t.locations,t.handleNumbers,t.connect)}function ue(e,t){t.handle&&(m(t.handle,r.cssClasses.active),L-=1),t.listeners.forEach((function(e){j.removeEventListener(e[0],e[1])})),0===L&&(m(C,r.cssClasses.drag),Pe(),e.cursor&&(F.style.cursor="",F.removeEventListener("selectstart",o))),r.events.smoothSteps&&(t.handleNumbers.forEach((function(e){Ne(e,A[e],!0,!0,!1,!1)})),t.handleNumbers.forEach((function(e){be("update",e)}))),t.handleNumbers.forEach((function(e){be("change",e),be("set",e),be("end",e)}))}function ce(e,t){if(!t.handleNumbers.some(X)){var i;1===t.handleNumbers.length&&(i=f[t.handleNumbers[0]].children[0],L+=1,h(i,r.cssClasses.active)),e.stopPropagation();var n=[],a=re(P.move,j,le,{target:e.target,handle:i,connect:t.connect,listeners:n,startCalcPoint:e.calcPoint,baseSize:te(),pageOffset:e.pageOffset,handleNumbers:t.handleNumbers,buttonsProperty:e.buttons,locations:A.slice()}),s=re(P.end,j,ue,{target:e.target,handle:i,listeners:n,doNotReject:!0,handleNumbers:t.handleNumbers}),l=re("mouseout",j,se,{target:e.target,handle:i,listeners:n,doNotReject:!0,handleNumbers:t.handleNumbers});n.push.apply(n,a.concat(s,l)),e.cursor&&(F.style.cursor=getComputedStyle(e.target).cursor,f.length>1&&h(C,r.cssClasses.drag),F.addEventListener("selectstart",o,!1)),t.handleNumbers.forEach((function(e){be("start",e)}))}}function pe(e){e.stopPropagation();var t=ne(e.calcPoint),i=ae(t);!1!==i&&(r.events.snap||c(C,r.cssClasses.tap,r.animationDuration),Ne(i,t,!0,!0),Pe(),be("slide",i,!0),be("update",i,!0),r.events.snap?ce(e,{handleNumbers:[i]}):(be("change",i,!0),be("set",i,!0)))}function de(e){var t=ne(e.calcPoint),r=k.getStep(t),i=k.fromStepping(r);Object.keys(U).forEach((function(e){"hover"===e.split(".")[0]&&U[e].forEach((function(e){e.call(Te,i)}))}))}function fe(e,t){if($()||X(t))return!1;var i=["Left","Right"],n=["Down","Up"],o=["PageDown","PageUp"],a=["Home","End"];r.dir&&!r.ort?i.reverse():r.ort&&!r.dir&&(n.reverse(),o.reverse());var s,l=e.key.replace("Arrow",""),u=l===o[0],c=l===o[1],p=l===n[0]||l===i[0]||u,d=l===n[1]||l===i[1]||c,f=l===a[0],h=l===a[1];if(!(p||d||f||h))return!0;if(e.preventDefault(),d||p){var m=p?0:1,v=Oe(t)[m];if(null===v)return!1;!1===v&&(v=k.getDefaultStep(A[t],p,r.keyboardDefaultStep)),v*=c||u?r.keyboardPageMultiplier:r.keyboardMultiplier,v=Math.max(v,1e-7),v*=p?-1:1,s=V[t]+v}else s=h?r.spectrum.xVal[r.spectrum.xVal.length-1]:r.spectrum.xVal[0];return Ne(t,k.toStepping(s),!0,!0),be("slide",t),be("update",t),be("change",t),be("set",t),!1}function he(e){e.fixed||f.forEach((function(e,t){re(P.start,e.children[0],ce,{handleNumbers:[t]})})),e.tap&&re(P.start,u,pe,{}),e.hover&&re(P.move,u,de,{hover:!0}),e.drag&&x.forEach((function(t,i){if(!1!==t&&0!==i&&i!==x.length-1){var n=f[i-1],o=f[i],a=[t],s=[n,o],l=[i-1,i];h(t,r.cssClasses.draggable),e.fixed&&(a.push(n.children[0]),a.push(o.children[0])),e.dragAll&&(s=f,l=M),a.forEach((function(e){re(P.start,e,ce,{handles:s,handleNumbers:l,connect:t})}))}}))}function me(e,t){U[e]=U[e]||[],U[e].push(t),"update"===e.split(".")[0]&&f.forEach((function(e,t){be("update",t)}))}function ve(e){return e===D.aria||e===D.tooltips}function ge(e){var t=e&&e.split(".")[0],r=t?e.substring(t.length):e;Object.keys(U).forEach((function(e){var i=e.split(".")[0],n=e.substring(i.length);t&&t!==i||r&&r!==n||ve(n)&&r!==n||delete U[e]}))}function be(e,t,i){Object.keys(U).forEach((function(n){var o=n.split(".")[0];e===o&&U[n].forEach((function(e){e.call(Te,V.map(r.format.to),t,V.slice(),i||!1,A.slice(),Te)}))}))}function ye(e,t,i,n,o,a,s){var l;return f.length>1&&!r.events.unconstrained&&(n&&t>0&&(l=k.getAbsoluteDistance(e[t-1],r.margin,!1),i=Math.max(i,l)),o&&t1&&r.limit&&(n&&t>0&&(l=k.getAbsoluteDistance(e[t-1],r.limit,!1),i=Math.min(i,l)),o&&t1?n.forEach((function(e,r){var i=ye(a,e,a[e]+t,u[r],c[r],!1,l);!1===i?t=0:(t=i-a[e],a[e]=i)})):u=c=[!0];var p=!1;n.forEach((function(e,r){p=Ne(e,i[e]+t,u[r],c[r],!1,l)||p})),p&&(n.forEach((function(e){be("update",e),be("slide",e)})),null!=o&&be("drag",s))}function we(e,t){return r.dir?100-e-t:e}function Ee(e,t){A[e]=t,V[e]=k.fromStepping(t);var i="translate("+Se(we(t,0)-T+"%","0")+")";f[e].style[r.transformRule]=i,Ce(e),Ce(e+1)}function Pe(){M.forEach((function(e){var t=A[e]>50?-1:1,r=3+(f.length+t*e);f[e].style.zIndex=String(r)}))}function Ne(e,t,r,i,n,o){return n||(t=ye(A,e,t,r,i,!1,o)),!1!==t&&(Ee(e,t),!0)}function Ce(e){if(x[e]){var t=0,i=100;0!==e&&(t=A[e-1]),e!==x.length-1&&(i=A[e]);var n=i-t,o="translate("+Se(we(t,n)+"%","0")+")",a="scale("+Se(n/100,"1")+")";x[e].style[r.transformRule]=o+" "+a}}function ke(e,t){return null===e||!1===e||void 0===e?A[t]:("number"==typeof e&&(e=String(e)),!1!==(e=r.format.from(e))&&(e=k.toStepping(e)),!1===e||isNaN(e)?A[t]:e)}function Ve(e,t,i){var n=d(e),o=void 0===A[0];t=void 0===t||t,r.animate&&!o&&c(C,r.cssClasses.tap,r.animationDuration),M.forEach((function(e){Ne(e,ke(n[e],e),!0,!1,i)}));var a=1===M.length?0:1;if(o&&k.hasNoSize()&&(i=!0,A[0]=0,M.length>1)){var s=100/(M.length-1);M.forEach((function(e){A[e]=e*s}))}for(;a=0&&ei.stepAfter.startValue&&(o=i.stepAfter.startValue-n),a=n>i.thisStep.startValue?i.thisStep.step:!1!==i.stepBefore.step&&n-i.stepBefore.highestStep,100===t?o=null:0===t&&(a=null);var s=k.countStepDecimals();return null!==o&&!1!==o&&(o=Number(o.toFixed(s))),null!==a&&!1!==a&&(a=Number(a.toFixed(s))),[a,o]}function De(){return M.map(Oe)}function je(e,t){var i=Le(),o=["margin","limit","padding","range","animate","snap","step","format","pips","tooltips"];o.forEach((function(t){void 0!==e[t]&&(s[t]=e[t])}));var a=oe(s);o.forEach((function(t){void 0!==e[t]&&(r[t]=a[t])})),k=a.spectrum,r.margin=a.margin,r.limit=a.limit,r.padding=a.padding,r.pips?ee(r.pips):Z(),r.tooltips?I():Y(),A=[],Ve(n(e.start)?e.start:i,t)}function Fe(){u=B(C),R(r.connect,u),he(r.events),Ve(r.start),r.pips&&ee(r.pips),r.tooltips&&I(),W()}Fe();var Te={destroy:Ue,steps:De,on:me,off:ge,get:Le,set:Ve,setHandle:Me,reset:Ae,__moveHandles:function(e,t,r){xe(e,t,A,r)},options:s,updateOptions:je,target:C,removePips:Z,removeTooltips:Y,getPositions:function(){return A.slice()},getTooltips:function(){return E},getOrigins:function(){return f},pips:ee};return Te}function se(e,t){if(!e||!e.nodeName)throw new Error("noUiSlider: create requires a single element, got: "+e);if(e.noUiSlider)throw new Error("noUiSlider: Slider was already initialized.");var r=ae(e,oe(t),t);return e.noUiSlider=r,r}var le={__spectrum:L,cssClasses:O,create:se};e.create=se,e.cssClasses=O,e.default=le,Object.defineProperty(e,"__esModule",{value:!0})}(t)})));function a(e,t){if(!Array.isArray(e)||!Array.isArray(t))return!1;const r=t.slice().sort();return e.length===t.length&&e.slice().sort().every((function(e,t){return e===r[t]}))}var s={name:"Slider",emits:["input","update:modelValue","start","slide","drag","update","change","set","end"],props:{...{value:{validator:function(e){return e=>"number"==typeof e||e instanceof Array||null==e||!1===e},required:!1},modelValue:{validator:function(e){return e=>"number"==typeof e||e instanceof Array||null==e||!1===e},required:!1}},id:{type:[String,Number],required:!1},disabled:{type:Boolean,required:!1,default:!1},min:{type:Number,required:!1,default:0},max:{type:Number,required:!1,default:100},step:{type:Number,required:!1,default:1},orientation:{type:String,required:!1,default:"horizontal"},direction:{type:String,required:!1,default:"ltr"},tooltips:{type:Boolean,required:!1,default:!0},options:{type:Object,required:!1,default:()=>({})},merge:{type:Number,required:!1,default:-1},format:{type:[Object,Function,Boolean],required:!1,default:null},classes:{type:Object,required:!1,default:()=>({})},showTooltip:{type:String,required:!1,default:"always"},tooltipPosition:{type:String,required:!1,default:null},lazy:{type:Boolean,required:!1,default:!0},ariaLabelledby:{type:String,required:!1,default:void 0},aria:{required:!1,type:Object,default:()=>({})}},setup(r,i){const s=function(r,i,n){const{value:o,modelValue:a,min:s}=e.toRefs(r);let l=a&&void 0!==a.value?a:o;const u=e.ref(l.value);if(t(l.value)&&(l=e.ref(s.value)),Array.isArray(l.value)&&0==l.value.length)throw new Error("Slider v-model must not be an empty array");return{value:l,initialValue:u}}(r),l=function(t,r,i){const{classes:n,showTooltip:o,tooltipPosition:a,orientation:s}=e.toRefs(t),l=e.computed((()=>({target:"slider-target",focused:"slider-focused",tooltipFocus:"slider-tooltip-focus",tooltipDrag:"slider-tooltip-drag",ltr:"slider-ltr",rtl:"slider-rtl",horizontal:"slider-horizontal",vertical:"slider-vertical",textDirectionRtl:"slider-txt-dir-rtl",textDirectionLtr:"slider-txt-dir-ltr",base:"slider-base",connects:"slider-connects",connect:"slider-connect",origin:"slider-origin",handle:"slider-handle",handleLower:"slider-handle-lower",handleUpper:"slider-handle-upper",touchArea:"slider-touch-area",tooltip:"slider-tooltip",tooltipTop:"slider-tooltip-top",tooltipBottom:"slider-tooltip-bottom",tooltipLeft:"slider-tooltip-left",tooltipRight:"slider-tooltip-right",tooltipHidden:"slider-tooltip-hidden",active:"slider-active",draggable:"slider-draggable",tap:"slider-state-tap",drag:"slider-state-drag",pips:"slider-pips",pipsHorizontal:"slider-pips-horizontal",pipsVertical:"slider-pips-vertical",marker:"slider-marker",markerHorizontal:"slider-marker-horizontal",markerVertical:"slider-marker-vertical",markerNormal:"slider-marker-normal",markerLarge:"slider-marker-large",markerSub:"slider-marker-sub",value:"slider-value",valueHorizontal:"slider-value-horizontal",valueVertical:"slider-value-vertical",valueNormal:"slider-value-normal",valueLarge:"slider-value-large",valueSub:"slider-value-sub",...n.value})));return{classList:e.computed((()=>{const e={...l.value};return Object.keys(e).forEach((t=>{e[t]=Array.isArray(e[t])?e[t].filter((e=>null!==e)).join(" "):e[t]})),"always"!==o.value&&(e.target+=` ${"drag"===o.value?e.tooltipDrag:e.tooltipFocus}`),"horizontal"===s.value&&(e.tooltip+="bottom"===a.value?` ${e.tooltipBottom}`:` ${e.tooltipTop}`),"vertical"===s.value&&(e.tooltip+="right"===a.value?` ${e.tooltipRight}`:` ${e.tooltipLeft}`),e}))}}(r),u=function(t,r,i){const{format:o,step:a}=e.toRefs(t),s=i.value,l=i.classList,u=e.computed((()=>o&&o.value?"function"==typeof o.value?{to:o.value}:n({...o.value}):n({decimals:a.value>=0?0:2}))),c=e.computed((()=>Array.isArray(s.value)?s.value.map((e=>u.value)):u.value));return{tooltipFormat:u,tooltipsFormat:c,tooltipsMerge:(e,t,r)=>{var i="rtl"===getComputedStyle(e).direction,n="rtl"===e.noUiSlider.options.direction,o="vertical"===e.noUiSlider.options.orientation,a=e.noUiSlider.getTooltips(),s=e.noUiSlider.getOrigins();a.forEach((function(e,t){e&&s[t].appendChild(e)})),e.noUiSlider.on("update",(function(e,s,c,p,d){var f=[[]],h=[[]],m=[[]],v=0;a[0]&&(f[0][0]=0,h[0][0]=d[0],m[0][0]=u.value.to(parseFloat(e[0])));for(var g=1;gt)&&(f[++v]=[],m[v]=[],h[v]=[]),a[g]&&(f[v].push(g),m[v].push(u.value.to(parseFloat(e[g]))),h[v].push(d[g]));f.forEach((function(e,t){for(var s=e.length,u=0;u{a[c].classList.contains(e)&&a[c].classList.remove(e)}))}else a[c].style.display="none",l.value.tooltipHidden.split(" ").forEach((e=>{a[c].classList.add(e)}))}}))}))}}}(r,0,{value:s.value,classList:l.classList}),c=function(r,i,n){const{orientation:s,direction:l,tooltips:u,step:c,min:p,max:d,merge:f,id:h,disabled:m,options:v,classes:g,format:b,lazy:y,ariaLabelledby:S,aria:x}=e.toRefs(r),w=n.value,E=n.initialValue,P=n.tooltipsFormat,N=n.tooltipsMerge,C=n.tooltipFormat,k=n.classList,V=e.ref(null),A=e.ref(null),M=e.ref(!1),L=e.computed((()=>{let e={cssPrefix:"",cssClasses:k.value,orientation:s.value,direction:l.value,tooltips:!!u.value&&P.value,connect:"lower",start:t(w.value)?p.value:w.value,range:{min:p.value,max:d.value}};if(c.value>0&&(e.step=c.value),Array.isArray(w.value)&&(e.connect=!0),S&&S.value||x&&Object.keys(x.value).length){let t=Array.isArray(w.value)?w.value:[w.value];e.handleAttributes=t.map((e=>Object.assign({},x.value,S&&S.value?{"aria-labelledby":S.value}:{})))}return b.value&&(e.ariaFormat=C.value),e})),U=e.computed((()=>{let e={id:h&&h.value?h.value:void 0};return m.value&&(e.disabled=!0),e})),O=e.computed((()=>Array.isArray(w.value))),D=()=>{let e=A.value.get();return Array.isArray(e)?e.map((e=>parseFloat(e))):parseFloat(e)},j=function(e){let t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];A.value.set(e,t)},F=e=>{i.emit("input",e),i.emit("update:modelValue",e),i.emit("update",e)},T=()=>{A.value=o.create(V.value,Object.assign({},L.value,v.value)),u.value&&O.value&&f.value>=0&&N(V.value,f.value," - "),A.value.on("set",(()=>{const e=D();i.emit("change",e),i.emit("set",e),y.value&&F(e)})),A.value.on("update",(()=>{if(!M.value)return;const e=D();O.value&&a(w.value,e)||!O.value&&w.value==e?i.emit("update",e):y.value||F(e)})),A.value.on("start",(()=>{i.emit("start",D())})),A.value.on("end",(()=>{i.emit("end",D())})),A.value.on("slide",(()=>{i.emit("slide",D())})),A.value.on("drag",(()=>{i.emit("drag",D())})),V.value.querySelectorAll("[data-handle]").forEach((e=>{e.onblur=()=>{V.value&&k.value.focused.split(" ").forEach((e=>{V.value.classList.remove(e)}))},e.onfocus=()=>{k.value.focused.split(" ").forEach((e=>{V.value.classList.add(e)}))}})),M.value=!0},z=()=>{A.value.off(),A.value.destroy(),A.value=null},H=(e,t)=>{M.value=!1,z(),T()};return e.onMounted(T),e.onUnmounted(z),e.watch(O,H,{immediate:!1}),e.watch(p,H,{immediate:!1}),e.watch(d,H,{immediate:!1}),e.watch(c,H,{immediate:!1}),e.watch(s,H,{immediate:!1}),e.watch(l,H,{immediate:!1}),e.watch(u,H,{immediate:!1}),e.watch(f,H,{immediate:!1}),e.watch(b,H,{immediate:!1,deep:!0}),e.watch(v,H,{immediate:!1,deep:!0}),e.watch(g,H,{immediate:!1,deep:!0}),e.watch(w,((e,r)=>{r&&("object"==typeof r&&"object"==typeof e&&e&&Object.keys(r)>Object.keys(e)||"object"==typeof r&&"object"!=typeof e||t(e))&&H()}),{immediate:!1}),e.watch(w,(e=>{if(t(e))return void j(p.value,!1);let r=D();O.value&&!Array.isArray(r)&&(r=[r]),(O.value&&!a(e,r)||!O.value&&e!=r)&&j(e,!1)}),{deep:!0}),{slider:V,slider$:A,isRange:O,sliderProps:U,init:T,destroy:z,refresh:H,update:j,reset:()=>{F(E.value)}}}(r,i,{value:s.value,initialValue:s.initialValue,tooltipFormat:u.tooltipFormat,tooltipsFormat:u.tooltipsFormat,tooltipsMerge:u.tooltipsMerge,classList:l.classList});return{...l,...u,...c}}};return s.render=function(t,r,i,n,o,a){return e.openBlock(),e.createElementBlock("div",e.mergeProps(t.sliderProps,{ref:"slider"}),null,16)},s.__file="src/Slider.vue",s}(Vue); 2 | -------------------------------------------------------------------------------- /dist/slider.js: -------------------------------------------------------------------------------- 1 | import{toRefs as e,ref as t,computed as r,onMounted as i,onUnmounted as n,watch as o,openBlock as a,createElementBlock as s,mergeProps as l}from"vue";function u(e){return-1!==[null,void 0,!1].indexOf(e)}"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self&&self;function c(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function p(e){var t={exports:{}};return e(t,t.exports),t.exports}var d=p((function(e,t){e.exports=function(){var e=["decimals","thousand","mark","prefix","suffix","encoder","decoder","negativeBefore","negative","edit","undo"];function t(e){return e.split("").reverse().join("")}function r(e,t){return e.substring(0,t.length)===t}function i(e,t){return e.slice(-1*t.length)===t}function n(e,t,r){if((e[t]||e[r])&&e[t]===e[r])throw new Error(t)}function o(e){return"number"==typeof e&&isFinite(e)}function a(e,t){return e=e.toString().split("e"),(+((e=(e=Math.round(+(e[0]+"e"+(e[1]?+e[1]+t:t)))).toString().split("e"))[0]+"e"+(e[1]?+e[1]-t:-t))).toFixed(t)}function s(e,r,i,n,s,l,u,c,p,d,f,h){var m,v,g,b=h,y="",S="";return l&&(h=l(h)),!!o(h)&&(!1!==e&&0===parseFloat(h.toFixed(e))&&(h=0),h<0&&(m=!0,h=Math.abs(h)),!1!==e&&(h=a(h,e)),-1!==(h=h.toString()).indexOf(".")?(g=(v=h.split("."))[0],i&&(y=i+v[1])):g=h,r&&(g=t(g).match(/.{1,3}/g),g=t(g.join(t(r)))),m&&c&&(S+=c),n&&(S+=n),m&&p&&(S+=p),S+=g,S+=y,s&&(S+=s),d&&(S=d(S,b)),S)}function l(e,t,n,a,s,l,u,c,p,d,f,h){var m,v="";return f&&(h=f(h)),!(!h||"string"!=typeof h)&&(c&&r(h,c)&&(h=h.replace(c,""),m=!0),a&&r(h,a)&&(h=h.replace(a,"")),p&&r(h,p)&&(h=h.replace(p,""),m=!0),s&&i(h,s)&&(h=h.slice(0,-1*s.length)),t&&(h=h.split(t).join("")),n&&(h=h.replace(n,".")),m&&(v+="-"),""!==(v=(v+=h).replace(/[^0-9\.\-.]/g,""))&&(v=Number(v),u&&(v=u(v)),!!o(v)&&v))}function u(t){var r,i,o,a={};for(void 0===t.suffix&&(t.suffix=t.postfix),r=0;r=0&&o<8))throw new Error(i);a[i]=o}else if("encoder"===i||"decoder"===i||"edit"===i||"undo"===i){if("function"!=typeof o)throw new Error(i);a[i]=o}else{if("string"!=typeof o)throw new Error(i);a[i]=o}return n(a,"mark","thousand"),n(a,"prefix","negative"),n(a,"prefix","negativeBefore"),a}function c(t,r,i){var n,o=[];for(n=0;n0&&(h(e,t),setTimeout((function(){m(e,t)}),r))}function p(e){return Math.max(Math.min(e,100),0)}function d(e){return Array.isArray(e)?e:[e]}function f(e){var t=(e=String(e)).split(".");return t.length>1?t[1].length:0}function h(e,t){e.classList&&!/\s/.test(t)?e.classList.add(t):e.className+=" "+t}function m(e,t){e.classList&&!/\s/.test(t)?e.classList.remove(t):e.className=e.className.replace(new RegExp("(^|\\b)"+t.split(" ").join("|")+"(\\b|$)","gi")," ")}function v(e,t){return e.classList?e.classList.contains(t):new RegExp("\\b"+t+"\\b").test(e.className)}function g(e){var t=void 0!==window.pageXOffset,r="CSS1Compat"===(e.compatMode||"");return{x:t?window.pageXOffset:r?e.documentElement.scrollLeft:e.body.scrollLeft,y:t?window.pageYOffset:r?e.documentElement.scrollTop:e.body.scrollTop}}function b(){return window.navigator.pointerEnabled?{start:"pointerdown",move:"pointermove",end:"pointerup"}:window.navigator.msPointerEnabled?{start:"MSPointerDown",move:"MSPointerMove",end:"MSPointerUp"}:{start:"mousedown touchstart",move:"mousemove touchmove",end:"mouseup touchend"}}function y(){var e=!1;try{var t=Object.defineProperty({},"passive",{get:function(){e=!0}});window.addEventListener("test",null,t)}catch(e){}return e}function S(){return window.CSS&&CSS.supports&&CSS.supports("touch-action","none")}function x(e,t){return 100/(t-e)}function w(e,t,r){return 100*t/(e[r+1]-e[r])}function E(e,t){return w(e,e[0]<0?t+Math.abs(e[0]):t-e[0],0)}function P(e,t){return t*(e[1]-e[0])/100+e[0]}function N(e,t){for(var r=1;e>=t[r];)r+=1;return r}function C(e,t,r){if(r>=e.slice(-1)[0])return 100;var i=N(r,e),n=e[i-1],o=e[i],a=t[i-1],s=t[i];return a+E([n,o],r)/x(a,s)}function k(e,t,r){if(r>=100)return e.slice(-1)[0];var i=N(r,t),n=e[i-1],o=e[i],a=t[i-1];return P([n,o],(r-a)*x(a,t[i]))}function V(e,t,r,i){if(100===i)return i;var n=N(i,e),o=e[n-1],a=e[n];return r?i-o>(a-o)/2?a:o:t[n-1]?e[n-1]+s(i-e[n-1],t[n-1]):i}var A,M;e.PipsMode=void 0,(M=e.PipsMode||(e.PipsMode={})).Range="range",M.Steps="steps",M.Positions="positions",M.Count="count",M.Values="values",e.PipsType=void 0,(A=e.PipsType||(e.PipsType={}))[A.None=-1]="None",A[A.NoValue=0]="NoValue",A[A.LargeValue=1]="LargeValue",A[A.SmallValue=2]="SmallValue";var L=function(){function e(e,t,r){var i;this.xPct=[],this.xVal=[],this.xSteps=[],this.xNumSteps=[],this.xHighestCompleteStep=[],this.xSteps=[r||!1],this.xNumSteps=[!1],this.snap=t;var n=[];for(Object.keys(e).forEach((function(t){n.push([d(e[t]),t])})),n.sort((function(e,t){return e[0][0]-t[0][0]})),i=0;ithis.xPct[n+1];)n++;else e===this.xPct[this.xPct.length-1]&&(n=this.xPct.length-2);r||e!==this.xPct[n+1]||n++,null===t&&(t=[]);var o=1,a=t[n],s=0,l=0,u=0,c=0;for(i=r?(e-this.xPct[n])/(this.xPct[n+1]-this.xPct[n]):(this.xPct[n+1]-e)/(this.xPct[n+1]-this.xPct[n]);a>0;)s=this.xPct[n+1+c]-this.xPct[n+c],t[n+c]*o+100-100*i>100?(l=s*i,o=(a-100*i)/t[n+c],i=1):(l=t[n+c]*s/100*o,o=0),r?(u-=l,this.xPct.length+c>=1&&c--):(u+=l,this.xPct.length-c>=1&&c++),a=t[n+c]*o;return e+u},e.prototype.toStepping=function(e){return e=C(this.xVal,this.xPct,e)},e.prototype.fromStepping=function(e){return k(this.xVal,this.xPct,e)},e.prototype.getStep=function(e){return e=V(this.xPct,this.xSteps,this.snap,e)},e.prototype.getDefaultStep=function(e,t,r){var i=N(e,this.xPct);return(100===e||t&&e===this.xPct[i-1])&&(i=Math.max(i-1,1)),(this.xVal[i]-this.xVal[i-1])/r},e.prototype.getNearbySteps=function(e){var t=N(e,this.xPct);return{stepBefore:{startValue:this.xVal[t-2],step:this.xNumSteps[t-2],highestStep:this.xHighestCompleteStep[t-2]},thisStep:{startValue:this.xVal[t-1],step:this.xNumSteps[t-1],highestStep:this.xHighestCompleteStep[t-1]},stepAfter:{startValue:this.xVal[t],step:this.xNumSteps[t],highestStep:this.xHighestCompleteStep[t]}}},e.prototype.countStepDecimals=function(){var e=this.xNumSteps.map(f);return Math.max.apply(null,e)},e.prototype.hasNoSize=function(){return this.xVal[0]===this.xVal[this.xVal.length-1]},e.prototype.convert=function(e){return this.getStep(this.toStepping(e))},e.prototype.handleEntryPoint=function(e,t){var r;if(!u(r="min"===e?0:"max"===e?100:parseFloat(e))||!u(t[0]))throw new Error("noUiSlider: 'range' value isn't numeric.");this.xPct.push(r),this.xVal.push(t[0]);var i=Number(t[1]);r?this.xSteps.push(!isNaN(i)&&i):isNaN(i)||(this.xSteps[0]=i),this.xHighestCompleteStep.push(0)},e.prototype.handleStepPoint=function(e,t){if(t)if(this.xVal[e]!==this.xVal[e+1]){this.xSteps[e]=w([this.xVal[e],this.xVal[e+1]],t,0)/x(this.xPct[e],this.xPct[e+1]);var r=(this.xVal[e+1]-this.xVal[e])/this.xNumSteps[e],i=Math.ceil(Number(r.toFixed(3))-1),n=this.xVal[e]+this.xNumSteps[e]*i;this.xHighestCompleteStep[e]=n}else this.xSteps[e]=this.xHighestCompleteStep[e]=this.xVal[e]},e}(),U={to:function(e){return void 0===e?"":e.toFixed(2)},from:Number},O={target:"target",base:"base",origin:"origin",handle:"handle",handleLower:"handle-lower",handleUpper:"handle-upper",touchArea:"touch-area",horizontal:"horizontal",vertical:"vertical",background:"background",connect:"connect",connects:"connects",ltr:"ltr",rtl:"rtl",textDirectionLtr:"txt-dir-ltr",textDirectionRtl:"txt-dir-rtl",draggable:"draggable",drag:"state-drag",tap:"state-tap",active:"active",tooltip:"tooltip",pips:"pips",pipsHorizontal:"pips-horizontal",pipsVertical:"pips-vertical",marker:"marker",markerHorizontal:"marker-horizontal",markerVertical:"marker-vertical",markerNormal:"marker-normal",markerLarge:"marker-large",markerSub:"marker-sub",value:"value",valueHorizontal:"value-horizontal",valueVertical:"value-vertical",valueNormal:"value-normal",valueLarge:"value-large",valueSub:"value-sub"},D={tooltips:".__tooltips",aria:".__aria"};function j(e,t){if(!u(t))throw new Error("noUiSlider: 'step' is not numeric.");e.singleStep=t}function F(e,t){if(!u(t))throw new Error("noUiSlider: 'keyboardPageMultiplier' is not numeric.");e.keyboardPageMultiplier=t}function T(e,t){if(!u(t))throw new Error("noUiSlider: 'keyboardMultiplier' is not numeric.");e.keyboardMultiplier=t}function z(e,t){if(!u(t))throw new Error("noUiSlider: 'keyboardDefaultStep' is not numeric.");e.keyboardDefaultStep=t}function H(e,t){if("object"!=typeof t||Array.isArray(t))throw new Error("noUiSlider: 'range' is not an object.");if(void 0===t.min||void 0===t.max)throw new Error("noUiSlider: Missing 'min' or 'max' in 'range'.");e.spectrum=new L(t,e.snap||!1,e.singleStep)}function q(e,t){if(t=d(t),!Array.isArray(t)||!t.length)throw new Error("noUiSlider: 'start' option is incorrect.");e.handles=t.length,e.start=t}function R(e,t){if("boolean"!=typeof t)throw new Error("noUiSlider: 'snap' option must be a boolean.");e.snap=t}function B(e,t){if("boolean"!=typeof t)throw new Error("noUiSlider: 'animate' option must be a boolean.");e.animate=t}function _(e,t){if("number"!=typeof t)throw new Error("noUiSlider: 'animationDuration' option must be a number.");e.animationDuration=t}function $(e,t){var r,i=[!1];if("lower"===t?t=[!0,!1]:"upper"===t&&(t=[!1,!0]),!0===t||!1===t){for(r=1;r1)throw new Error("noUiSlider: 'padding' option must not exceed 100% of the range.")}}function G(e,t){switch(t){case"ltr":e.dir=0;break;case"rtl":e.dir=1;break;default:throw new Error("noUiSlider: 'direction' option was not recognized.")}}function J(e,t){if("string"!=typeof t)throw new Error("noUiSlider: 'behaviour' must be a string containing options.");var r=t.indexOf("tap")>=0,i=t.indexOf("drag")>=0,n=t.indexOf("fixed")>=0,o=t.indexOf("snap")>=0,a=t.indexOf("hover")>=0,s=t.indexOf("unconstrained")>=0,l=t.indexOf("drag-all")>=0,u=t.indexOf("smooth-steps")>=0;if(n){if(2!==e.handles)throw new Error("noUiSlider: 'fixed' behaviour must be used with 2 handles");Y(e,e.start[1]-e.start[0])}if(s&&(e.margin||e.limit))throw new Error("noUiSlider: 'unconstrained' behaviour cannot be used with margin or limit");e.events={tap:r||o,drag:i,dragAll:l,smoothSteps:u,fixed:n,snap:o,hover:a,unconstrained:s}}function K(e,t){if(!1!==t)if(!0===t||r(t)){e.tooltips=[];for(var i=0;i= 2) required for mode 'count'.");for(var r=t.values-1,i=100/r,n=[];r--;)n[r]=r*i;return n.push(100),J(n,t.stepped)}return t.mode===e.PipsMode.Positions?J(t.values,t.stepped):t.mode===e.PipsMode.Values?t.stepped?t.values.map((function(e){return k.fromStepping(k.getStep(k.toStepping(e)))})):t.values:[]}function J(e,t){return e.map((function(e){return k.fromStepping(t?k.getStep(e):e)}))}function K(t){function r(e,t){return Number((e+t).toFixed(7))}var i=G(t),n={},o=k.xVal[0],s=k.xVal[k.xVal.length-1],l=!1,u=!1,c=0;return(i=a(i.slice().sort((function(e,t){return e-t}))))[0]!==o&&(i.unshift(o),l=!0),i[i.length-1]!==s&&(i.push(s),u=!0),i.forEach((function(o,a){var s,p,d,f,h,m,v,g,b,y,S=o,x=i[a+1],w=t.mode===e.PipsMode.Steps;for(w&&(s=k.xNumSteps[a]),s||(s=x-S),void 0===x&&(x=S),s=Math.max(s,1e-7),p=S;p<=x;p=r(p,s)){for(g=(h=(f=k.toStepping(p))-c)/(t.density||1),y=h/(b=Math.round(g)),d=1;d<=b;d+=1)n[(m=c+d*y).toFixed(5)]=[k.fromStepping(m),0];v=i.indexOf(p)>-1?e.PipsType.LargeValue:w?e.PipsType.SmallValue:e.PipsType.NoValue,!a&&l&&p!==x&&(v=0),p===x&&u||(n[f.toFixed(5)]=[p,v]),c=f}})),n}function Q(t,i,n){var o,a,s=O.createElement("div"),l=((o={})[e.PipsType.None]="",o[e.PipsType.NoValue]=r.cssClasses.valueNormal,o[e.PipsType.LargeValue]=r.cssClasses.valueLarge,o[e.PipsType.SmallValue]=r.cssClasses.valueSub,o),u=((a={})[e.PipsType.None]="",a[e.PipsType.NoValue]=r.cssClasses.markerNormal,a[e.PipsType.LargeValue]=r.cssClasses.markerLarge,a[e.PipsType.SmallValue]=r.cssClasses.markerSub,a),c=[r.cssClasses.valueHorizontal,r.cssClasses.valueVertical],p=[r.cssClasses.markerHorizontal,r.cssClasses.markerVertical];function d(e,t){var i=t===r.cssClasses.value,n=i?l:u;return t+" "+(i?c:p)[r.ort]+" "+n[e]}function f(t,o,a){if((a=i?i(o,a):a)!==e.PipsType.None){var l=z(s,!1);l.className=d(a,r.cssClasses.marker),l.style[r.style]=t+"%",a>e.PipsType.NoValue&&((l=z(s,!1)).className=d(a,r.cssClasses.value),l.setAttribute("data-value",String(o)),l.style[r.style]=t+"%",l.innerHTML=String(n.to(o)))}}return h(s,r.cssClasses.pips),h(s,0===r.ort?r.cssClasses.pipsHorizontal:r.cssClasses.pipsVertical),Object.keys(t).forEach((function(e){f(e,t[e][0],t[e][1])})),s}function Z(){w&&(i(w),w=null)}function ee(e){Z();var t=K(e),r=e.filter,i=e.format||{to:function(e){return String(Math.round(e))}};return w=C.appendChild(Q(t,r,i))}function te(){var e=u.getBoundingClientRect(),t="offset"+["Width","Height"][r.ort];return 0===r.ort?e.width||u[t]:e.height||u[t]}function re(e,t,i,n){var o=function(o){var a=ie(o,n.pageOffset,n.target||t);return!!a&&!($()&&!n.doNotReject)&&!(v(C,r.cssClasses.tap)&&!n.doNotReject)&&!(e===P.start&&void 0!==a.buttons&&a.buttons>1)&&(!n.hover||!a.buttons)&&(N||a.preventDefault(),a.calcPoint=a.points[r.ort],void i(a,n))},a=[];return e.split(" ").forEach((function(e){t.addEventListener(e,o,!!N&&{passive:!0}),a.push([e,o])})),a}function ie(e,t,r){var i=0===e.type.indexOf("touch"),n=0===e.type.indexOf("mouse"),o=0===e.type.indexOf("pointer"),a=0,s=0;if(0===e.type.indexOf("MSPointer")&&(o=!0),"mousedown"===e.type&&!e.buttons&&!e.touches)return!1;if(i){var l=function(t){var i=t.target;return i===r||r.contains(i)||e.composed&&e.composedPath().shift()===r};if("touchstart"===e.type){var u=Array.prototype.filter.call(e.touches,l);if(u.length>1)return!1;a=u[0].pageX,s=u[0].pageY}else{var c=Array.prototype.find.call(e.changedTouches,l);if(!c)return!1;a=c.pageX,s=c.pageY}}return t=t||g(O),(n||o)&&(a=e.clientX+t.x,s=e.clientY+t.y),e.pageOffset=t,e.points=[a,s],e.cursor=n||o,e}function ne(e){var t=100*(e-l(u,r.ort))/te();return t=p(t),r.dir?100-t:t}function ae(e){var t=100,r=!1;return f.forEach((function(i,n){if(!X(n)){var o=A[n],a=Math.abs(o-e);(ao||100===a&&100===t)&&(r=n,t=a)}})),r}function se(e,t){"mouseout"===e.type&&"HTML"===e.target.nodeName&&null===e.relatedTarget&&ue(e,t)}function le(e,t){if(-1===navigator.appVersion.indexOf("MSIE 9")&&0===e.buttons&&0!==t.buttonsProperty)return ue(e,t);var i=(r.dir?-1:1)*(e.calcPoint-t.startCalcPoint);xe(i>0,100*i/t.baseSize,t.locations,t.handleNumbers,t.connect)}function ue(e,t){t.handle&&(m(t.handle,r.cssClasses.active),L-=1),t.listeners.forEach((function(e){j.removeEventListener(e[0],e[1])})),0===L&&(m(C,r.cssClasses.drag),Pe(),e.cursor&&(F.style.cursor="",F.removeEventListener("selectstart",o))),r.events.smoothSteps&&(t.handleNumbers.forEach((function(e){Ne(e,A[e],!0,!0,!1,!1)})),t.handleNumbers.forEach((function(e){be("update",e)}))),t.handleNumbers.forEach((function(e){be("change",e),be("set",e),be("end",e)}))}function ce(e,t){if(!t.handleNumbers.some(X)){var i;1===t.handleNumbers.length&&(i=f[t.handleNumbers[0]].children[0],L+=1,h(i,r.cssClasses.active)),e.stopPropagation();var n=[],a=re(P.move,j,le,{target:e.target,handle:i,connect:t.connect,listeners:n,startCalcPoint:e.calcPoint,baseSize:te(),pageOffset:e.pageOffset,handleNumbers:t.handleNumbers,buttonsProperty:e.buttons,locations:A.slice()}),s=re(P.end,j,ue,{target:e.target,handle:i,listeners:n,doNotReject:!0,handleNumbers:t.handleNumbers}),l=re("mouseout",j,se,{target:e.target,handle:i,listeners:n,doNotReject:!0,handleNumbers:t.handleNumbers});n.push.apply(n,a.concat(s,l)),e.cursor&&(F.style.cursor=getComputedStyle(e.target).cursor,f.length>1&&h(C,r.cssClasses.drag),F.addEventListener("selectstart",o,!1)),t.handleNumbers.forEach((function(e){be("start",e)}))}}function pe(e){e.stopPropagation();var t=ne(e.calcPoint),i=ae(t);!1!==i&&(r.events.snap||c(C,r.cssClasses.tap,r.animationDuration),Ne(i,t,!0,!0),Pe(),be("slide",i,!0),be("update",i,!0),r.events.snap?ce(e,{handleNumbers:[i]}):(be("change",i,!0),be("set",i,!0)))}function de(e){var t=ne(e.calcPoint),r=k.getStep(t),i=k.fromStepping(r);Object.keys(U).forEach((function(e){"hover"===e.split(".")[0]&&U[e].forEach((function(e){e.call(Te,i)}))}))}function fe(e,t){if($()||X(t))return!1;var i=["Left","Right"],n=["Down","Up"],o=["PageDown","PageUp"],a=["Home","End"];r.dir&&!r.ort?i.reverse():r.ort&&!r.dir&&(n.reverse(),o.reverse());var s,l=e.key.replace("Arrow",""),u=l===o[0],c=l===o[1],p=l===n[0]||l===i[0]||u,d=l===n[1]||l===i[1]||c,f=l===a[0],h=l===a[1];if(!(p||d||f||h))return!0;if(e.preventDefault(),d||p){var m=p?0:1,v=Oe(t)[m];if(null===v)return!1;!1===v&&(v=k.getDefaultStep(A[t],p,r.keyboardDefaultStep)),v*=c||u?r.keyboardPageMultiplier:r.keyboardMultiplier,v=Math.max(v,1e-7),v*=p?-1:1,s=V[t]+v}else s=h?r.spectrum.xVal[r.spectrum.xVal.length-1]:r.spectrum.xVal[0];return Ne(t,k.toStepping(s),!0,!0),be("slide",t),be("update",t),be("change",t),be("set",t),!1}function he(e){e.fixed||f.forEach((function(e,t){re(P.start,e.children[0],ce,{handleNumbers:[t]})})),e.tap&&re(P.start,u,pe,{}),e.hover&&re(P.move,u,de,{hover:!0}),e.drag&&x.forEach((function(t,i){if(!1!==t&&0!==i&&i!==x.length-1){var n=f[i-1],o=f[i],a=[t],s=[n,o],l=[i-1,i];h(t,r.cssClasses.draggable),e.fixed&&(a.push(n.children[0]),a.push(o.children[0])),e.dragAll&&(s=f,l=M),a.forEach((function(e){re(P.start,e,ce,{handles:s,handleNumbers:l,connect:t})}))}}))}function me(e,t){U[e]=U[e]||[],U[e].push(t),"update"===e.split(".")[0]&&f.forEach((function(e,t){be("update",t)}))}function ve(e){return e===D.aria||e===D.tooltips}function ge(e){var t=e&&e.split(".")[0],r=t?e.substring(t.length):e;Object.keys(U).forEach((function(e){var i=e.split(".")[0],n=e.substring(i.length);t&&t!==i||r&&r!==n||ve(n)&&r!==n||delete U[e]}))}function be(e,t,i){Object.keys(U).forEach((function(n){var o=n.split(".")[0];e===o&&U[n].forEach((function(e){e.call(Te,V.map(r.format.to),t,V.slice(),i||!1,A.slice(),Te)}))}))}function ye(e,t,i,n,o,a,s){var l;return f.length>1&&!r.events.unconstrained&&(n&&t>0&&(l=k.getAbsoluteDistance(e[t-1],r.margin,!1),i=Math.max(i,l)),o&&t1&&r.limit&&(n&&t>0&&(l=k.getAbsoluteDistance(e[t-1],r.limit,!1),i=Math.min(i,l)),o&&t1?n.forEach((function(e,r){var i=ye(a,e,a[e]+t,u[r],c[r],!1,l);!1===i?t=0:(t=i-a[e],a[e]=i)})):u=c=[!0];var p=!1;n.forEach((function(e,r){p=Ne(e,i[e]+t,u[r],c[r],!1,l)||p})),p&&(n.forEach((function(e){be("update",e),be("slide",e)})),null!=o&&be("drag",s))}function we(e,t){return r.dir?100-e-t:e}function Ee(e,t){A[e]=t,V[e]=k.fromStepping(t);var i="translate("+Se(we(t,0)-T+"%","0")+")";f[e].style[r.transformRule]=i,Ce(e),Ce(e+1)}function Pe(){M.forEach((function(e){var t=A[e]>50?-1:1,r=3+(f.length+t*e);f[e].style.zIndex=String(r)}))}function Ne(e,t,r,i,n,o){return n||(t=ye(A,e,t,r,i,!1,o)),!1!==t&&(Ee(e,t),!0)}function Ce(e){if(x[e]){var t=0,i=100;0!==e&&(t=A[e-1]),e!==x.length-1&&(i=A[e]);var n=i-t,o="translate("+Se(we(t,n)+"%","0")+")",a="scale("+Se(n/100,"1")+")";x[e].style[r.transformRule]=o+" "+a}}function ke(e,t){return null===e||!1===e||void 0===e?A[t]:("number"==typeof e&&(e=String(e)),!1!==(e=r.format.from(e))&&(e=k.toStepping(e)),!1===e||isNaN(e)?A[t]:e)}function Ve(e,t,i){var n=d(e),o=void 0===A[0];t=void 0===t||t,r.animate&&!o&&c(C,r.cssClasses.tap,r.animationDuration),M.forEach((function(e){Ne(e,ke(n[e],e),!0,!1,i)}));var a=1===M.length?0:1;if(o&&k.hasNoSize()&&(i=!0,A[0]=0,M.length>1)){var s=100/(M.length-1);M.forEach((function(e){A[e]=e*s}))}for(;a=0&&ei.stepAfter.startValue&&(o=i.stepAfter.startValue-n),a=n>i.thisStep.startValue?i.thisStep.step:!1!==i.stepBefore.step&&n-i.stepBefore.highestStep,100===t?o=null:0===t&&(a=null);var s=k.countStepDecimals();return null!==o&&!1!==o&&(o=Number(o.toFixed(s))),null!==a&&!1!==a&&(a=Number(a.toFixed(s))),[a,o]}function De(){return M.map(Oe)}function je(e,t){var i=Le(),o=["margin","limit","padding","range","animate","snap","step","format","pips","tooltips"];o.forEach((function(t){void 0!==e[t]&&(s[t]=e[t])}));var a=oe(s);o.forEach((function(t){void 0!==e[t]&&(r[t]=a[t])})),k=a.spectrum,r.margin=a.margin,r.limit=a.limit,r.padding=a.padding,r.pips?ee(r.pips):Z(),r.tooltips?I():Y(),A=[],Ve(n(e.start)?e.start:i,t)}function Fe(){u=B(C),R(r.connect,u),he(r.events),Ve(r.start),r.pips&&ee(r.pips),r.tooltips&&I(),W()}Fe();var Te={destroy:Ue,steps:De,on:me,off:ge,get:Le,set:Ve,setHandle:Me,reset:Ae,__moveHandles:function(e,t,r){xe(e,t,A,r)},options:s,updateOptions:je,target:C,removePips:Z,removeTooltips:Y,getPositions:function(){return A.slice()},getTooltips:function(){return E},getOrigins:function(){return f},pips:ee};return Te}function se(e,t){if(!e||!e.nodeName)throw new Error("noUiSlider: create requires a single element, got: "+e);if(e.noUiSlider)throw new Error("noUiSlider: Slider was already initialized.");var r=ae(e,oe(t),t);return e.noUiSlider=r,r}var le={__spectrum:L,cssClasses:O,create:se};e.create=se,e.cssClasses=O,e.default=le,Object.defineProperty(e,"__esModule",{value:!0})}(t)})));function h(e,t){if(!Array.isArray(e)||!Array.isArray(t))return!1;const r=t.slice().sort();return e.length===t.length&&e.slice().sort().every((function(e,t){return e===r[t]}))}var m={name:"Slider",emits:["input","update:modelValue","start","slide","drag","update","change","set","end"],props:{...{value:{validator:function(e){return e=>"number"==typeof e||e instanceof Array||null==e||!1===e},required:!1},modelValue:{validator:function(e){return e=>"number"==typeof e||e instanceof Array||null==e||!1===e},required:!1}},id:{type:[String,Number],required:!1},disabled:{type:Boolean,required:!1,default:!1},min:{type:Number,required:!1,default:0},max:{type:Number,required:!1,default:100},step:{type:Number,required:!1,default:1},orientation:{type:String,required:!1,default:"horizontal"},direction:{type:String,required:!1,default:"ltr"},tooltips:{type:Boolean,required:!1,default:!0},options:{type:Object,required:!1,default:()=>({})},merge:{type:Number,required:!1,default:-1},format:{type:[Object,Function,Boolean],required:!1,default:null},classes:{type:Object,required:!1,default:()=>({})},showTooltip:{type:String,required:!1,default:"always"},tooltipPosition:{type:String,required:!1,default:null},lazy:{type:Boolean,required:!1,default:!0},ariaLabelledby:{type:String,required:!1,default:void 0},aria:{required:!1,type:Object,default:()=>({})}},setup(a,s){const l=function(r,i,n){const{value:o,modelValue:a,min:s}=e(r);let l=a&&void 0!==a.value?a:o;const c=t(l.value);if(u(l.value)&&(l=t(s.value)),Array.isArray(l.value)&&0==l.value.length)throw new Error("Slider v-model must not be an empty array");return{value:l,initialValue:c}}(a),c=function(t,i,n){const{classes:o,showTooltip:a,tooltipPosition:s,orientation:l}=e(t),u=r((()=>({target:"slider-target",focused:"slider-focused",tooltipFocus:"slider-tooltip-focus",tooltipDrag:"slider-tooltip-drag",ltr:"slider-ltr",rtl:"slider-rtl",horizontal:"slider-horizontal",vertical:"slider-vertical",textDirectionRtl:"slider-txt-dir-rtl",textDirectionLtr:"slider-txt-dir-ltr",base:"slider-base",connects:"slider-connects",connect:"slider-connect",origin:"slider-origin",handle:"slider-handle",handleLower:"slider-handle-lower",handleUpper:"slider-handle-upper",touchArea:"slider-touch-area",tooltip:"slider-tooltip",tooltipTop:"slider-tooltip-top",tooltipBottom:"slider-tooltip-bottom",tooltipLeft:"slider-tooltip-left",tooltipRight:"slider-tooltip-right",tooltipHidden:"slider-tooltip-hidden",active:"slider-active",draggable:"slider-draggable",tap:"slider-state-tap",drag:"slider-state-drag",pips:"slider-pips",pipsHorizontal:"slider-pips-horizontal",pipsVertical:"slider-pips-vertical",marker:"slider-marker",markerHorizontal:"slider-marker-horizontal",markerVertical:"slider-marker-vertical",markerNormal:"slider-marker-normal",markerLarge:"slider-marker-large",markerSub:"slider-marker-sub",value:"slider-value",valueHorizontal:"slider-value-horizontal",valueVertical:"slider-value-vertical",valueNormal:"slider-value-normal",valueLarge:"slider-value-large",valueSub:"slider-value-sub",...o.value})));return{classList:r((()=>{const e={...u.value};return Object.keys(e).forEach((t=>{e[t]=Array.isArray(e[t])?e[t].filter((e=>null!==e)).join(" "):e[t]})),"always"!==a.value&&(e.target+=` ${"drag"===a.value?e.tooltipDrag:e.tooltipFocus}`),"horizontal"===l.value&&(e.tooltip+="bottom"===s.value?` ${e.tooltipBottom}`:` ${e.tooltipTop}`),"vertical"===l.value&&(e.tooltip+="right"===s.value?` ${e.tooltipRight}`:` ${e.tooltipLeft}`),e}))}}(a),p=function(t,i,n){const{format:o,step:a}=e(t),s=n.value,l=n.classList,u=r((()=>o&&o.value?"function"==typeof o.value?{to:o.value}:d({...o.value}):d({decimals:a.value>=0?0:2}))),c=r((()=>Array.isArray(s.value)?s.value.map((e=>u.value)):u.value));return{tooltipFormat:u,tooltipsFormat:c,tooltipsMerge:(e,t,r)=>{var i="rtl"===getComputedStyle(e).direction,n="rtl"===e.noUiSlider.options.direction,o="vertical"===e.noUiSlider.options.orientation,a=e.noUiSlider.getTooltips(),s=e.noUiSlider.getOrigins();a.forEach((function(e,t){e&&s[t].appendChild(e)})),e.noUiSlider.on("update",(function(e,s,c,p,d){var f=[[]],h=[[]],m=[[]],v=0;a[0]&&(f[0][0]=0,h[0][0]=d[0],m[0][0]=u.value.to(parseFloat(e[0])));for(var g=1;gt)&&(f[++v]=[],m[v]=[],h[v]=[]),a[g]&&(f[v].push(g),m[v].push(u.value.to(parseFloat(e[g]))),h[v].push(d[g]));f.forEach((function(e,t){for(var s=e.length,u=0;u{a[c].classList.contains(e)&&a[c].classList.remove(e)}))}else a[c].style.display="none",l.value.tooltipHidden.split(" ").forEach((e=>{a[c].classList.add(e)}))}}))}))}}}(a,0,{value:l.value,classList:c.classList}),m=function(a,s,l){const{orientation:c,direction:p,tooltips:d,step:m,min:v,max:g,merge:b,id:y,disabled:S,options:x,classes:w,format:E,lazy:P,ariaLabelledby:N,aria:C}=e(a),k=l.value,V=l.initialValue,A=l.tooltipsFormat,M=l.tooltipsMerge,L=l.tooltipFormat,U=l.classList,O=t(null),D=t(null),j=t(!1),F=r((()=>{let e={cssPrefix:"",cssClasses:U.value,orientation:c.value,direction:p.value,tooltips:!!d.value&&A.value,connect:"lower",start:u(k.value)?v.value:k.value,range:{min:v.value,max:g.value}};if(m.value>0&&(e.step=m.value),Array.isArray(k.value)&&(e.connect=!0),N&&N.value||C&&Object.keys(C.value).length){let t=Array.isArray(k.value)?k.value:[k.value];e.handleAttributes=t.map((e=>Object.assign({},C.value,N&&N.value?{"aria-labelledby":N.value}:{})))}return E.value&&(e.ariaFormat=L.value),e})),T=r((()=>{let e={id:y&&y.value?y.value:void 0};return S.value&&(e.disabled=!0),e})),z=r((()=>Array.isArray(k.value))),H=()=>{let e=D.value.get();return Array.isArray(e)?e.map((e=>parseFloat(e))):parseFloat(e)},q=function(e){let t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];D.value.set(e,t)},R=e=>{s.emit("input",e),s.emit("update:modelValue",e),s.emit("update",e)},B=()=>{D.value=f.create(O.value,Object.assign({},F.value,x.value)),d.value&&z.value&&b.value>=0&&M(O.value,b.value," - "),D.value.on("set",(()=>{const e=H();s.emit("change",e),s.emit("set",e),P.value&&R(e)})),D.value.on("update",(()=>{if(!j.value)return;const e=H();z.value&&h(k.value,e)||!z.value&&k.value==e?s.emit("update",e):P.value||R(e)})),D.value.on("start",(()=>{s.emit("start",H())})),D.value.on("end",(()=>{s.emit("end",H())})),D.value.on("slide",(()=>{s.emit("slide",H())})),D.value.on("drag",(()=>{s.emit("drag",H())})),O.value.querySelectorAll("[data-handle]").forEach((e=>{e.onblur=()=>{O.value&&U.value.focused.split(" ").forEach((e=>{O.value.classList.remove(e)}))},e.onfocus=()=>{U.value.focused.split(" ").forEach((e=>{O.value.classList.add(e)}))}})),j.value=!0},_=()=>{D.value.off(),D.value.destroy(),D.value=null},$=(e,t)=>{j.value=!1,_(),B()};return i(B),n(_),o(z,$,{immediate:!1}),o(v,$,{immediate:!1}),o(g,$,{immediate:!1}),o(m,$,{immediate:!1}),o(c,$,{immediate:!1}),o(p,$,{immediate:!1}),o(d,$,{immediate:!1}),o(b,$,{immediate:!1}),o(E,$,{immediate:!1,deep:!0}),o(x,$,{immediate:!1,deep:!0}),o(w,$,{immediate:!1,deep:!0}),o(k,((e,t)=>{t&&("object"==typeof t&&"object"==typeof e&&e&&Object.keys(t)>Object.keys(e)||"object"==typeof t&&"object"!=typeof e||u(e))&&$()}),{immediate:!1}),o(k,(e=>{if(u(e))return void q(v.value,!1);let t=H();z.value&&!Array.isArray(t)&&(t=[t]),(z.value&&!h(e,t)||!z.value&&e!=t)&&q(e,!1)}),{deep:!0}),{slider:O,slider$:D,isRange:z,sliderProps:T,init:B,destroy:_,refresh:$,update:q,reset:()=>{R(V.value)}}}(a,s,{value:l.value,initialValue:l.initialValue,tooltipFormat:p.tooltipFormat,tooltipsFormat:p.tooltipsFormat,tooltipsMerge:p.tooltipsMerge,classList:c.classList});return{...c,...p,...m}}};m.render=function(e,t,r,i,n,o){return a(),s("div",l(e.sliderProps,{ref:"slider"}),null,16)},m.__file="src/Slider.vue";export{m as default}; 2 | -------------------------------------------------------------------------------- /jest/jest.config.vue2.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "rootDir": "../", 3 | "moduleFileExtensions": [ 4 | "js", 5 | "json", 6 | "vue" 7 | ], 8 | "testTimeout": 1000, 9 | "testEnvironment": "jsdom", 10 | "transform": { 11 | ".*\\.(vue)$": "vue-prev-jest", 12 | "^.+\\.js$": "babel-jest" 13 | }, 14 | "transformIgnorePatterns": [ 15 | "/node_modules/" 16 | ], 17 | "moduleNameMapper": { 18 | "^@vue/test-utils$": "/node_modules/vue-prev-test-utils", 19 | "^vue$": "/node_modules/vue-prev", 20 | '^vue-jest$': "/node_modules/vue-prev-jest", 21 | '^unit-test-helpers$': "/tests/unit/helpers/vue2" 22 | }, 23 | "collectCoverage": true, 24 | "collectCoverageFrom": [ 25 | "**/src/**/*.{js,vue}", 26 | ], 27 | "coverageReporters": [ 28 | "html", 29 | "text-summary", 30 | "clover", 31 | "json" 32 | ] 33 | } -------------------------------------------------------------------------------- /jest/jest.config.vue3.js: -------------------------------------------------------------------------------- 1 | var base = require('./jest.config.vue2.js') 2 | 3 | module.exports = Object.assign({}, base, { 4 | "transform": { 5 | ".*\\.(vue)$": "vue-next-jest", 6 | "^.+\\.js$": "babel-jest", 7 | }, 8 | "moduleNameMapper": { 9 | "^@vue/test-utils$": "/node_modules/vue-next-test-utils", 10 | "^vue$": "/node_modules/vue-next", 11 | '^vue-jest$': "/node_modules/vue-next-jest", 12 | '^unit-test-helpers$': "/tests/unit/helpers/vue3" 13 | }, 14 | }) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vueform/slider", 3 | "version": "2.1.10", 4 | "private": false, 5 | "description": "Vue 3 slider component with multihandles, tooltips merging and formatting.", 6 | "license": "MIT", 7 | "author": "Adam Berecz ", 8 | "main": "./dist/slider.js", 9 | "types": "./src/index.d.ts", 10 | "module": "./dist/slider.js", 11 | "unpkg": "./dist/slider.global.js", 12 | "style": "./themes/default.css", 13 | "exports": { 14 | ".": { 15 | "types": "./src/index.d.ts", 16 | "import": "./dist/slider.js", 17 | "require": "./dist/slider.js" 18 | }, 19 | "./src/*": "./src/*", 20 | "./dist/*": "./dist/*", 21 | "./themes/*": "./themes/*", 22 | "./tailwind": "./tailwind.js", 23 | "./tailwind.js": "./tailwind.js", 24 | "./package.json": "./package.json" 25 | }, 26 | "sideEffects": false, 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/vueform/slider.git" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/vueform/slider/issues" 33 | }, 34 | "scripts": { 35 | "build": "npm run build:vue2; npm run build:vue3; npm run build:themes", 36 | "build:vue2": "rollup --config build/vue2.rollup.config.js", 37 | "build:vue3": "rollup --config build/vue3.rollup.config.js", 38 | "build:themes": "rollup --config build/themes.rollup.config.js", 39 | "test": "jest --config=jest/jest.config.vue3.js; jest --config=jest/jest.config.vue2.js;", 40 | "test:vue2": "jest --verbose --config=jest/jest.config.vue2.js", 41 | "test:vue3": "jest --verbose --config=jest/jest.config.vue3.js" 42 | }, 43 | "devDependencies": { 44 | "@babel/core": "^7.18.9", 45 | "@babel/plugin-transform-modules-umd": "^7.12.1", 46 | "@babel/preset-env": "^7.18.9", 47 | "@rollup/plugin-alias": "^3.1.1", 48 | "@rollup/plugin-babel": "^5.2.2", 49 | "@rollup/plugin-commonjs": "^17.0.0", 50 | "@rollup/plugin-node-resolve": "^11.0.1", 51 | "@testing-library/jest-dom": "^5.11.5", 52 | "@vue/compiler-sfc": "^3.0.4", 53 | "autoprefixer": "^10.1.0", 54 | "babel-core": "^7.0.0-bridge.0", 55 | "babel-jest": "^27.3.1", 56 | "babel-plugin-rename-umd-globals": "^1.0.0", 57 | "flush-promises": "^1.0.2", 58 | "jest": "^27.3.1", 59 | "jest-environment-jsdom-sixteen": "^1.0.3", 60 | "node-sass": "^7.0.0", 61 | "nouislider": "^15.4.0", 62 | "postcss": "^8.2.1", 63 | "rollup": "^2.77.0", 64 | "rollup-plugin-postcss": "^4.0.0", 65 | "rollup-plugin-terser": "^7.0.2", 66 | "tailwindcss": "^2.2.2", 67 | "typescript": "^4.1.2", 68 | "vue-next": "npm:vue@^3.2.20", 69 | "vue-next-jest": "npm:@vue/vue3-jest@^27.0.0-alpha.1", 70 | "vue-next-rollup-plugin-vue": "npm:rollup-plugin-vue@^6.0.0", 71 | "vue-next-test-utils": "npm:@vue/test-utils@2.0.0-rc.16", 72 | "vue-prev": "npm:vue@^2.6.14", 73 | "vue-prev-composition-api": "npm:@vue/composition-api@^1.2.4", 74 | "vue-prev-jest": "npm:@vue/vue2-jest@^27.0.0-alpha.2", 75 | "vue-prev-rollup-plugin-vue": "npm:rollup-plugin-vue@^5.1.9", 76 | "vue-prev-test-utils": "npm:@vue/test-utils@1.2.2", 77 | "vue-template-compiler": "^2.7.8", 78 | "wnumb": "^1.2.0" 79 | }, 80 | "browserslist": [ 81 | "> 1%", 82 | "last 2 versions", 83 | "not dead" 84 | ] 85 | } 86 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {} -------------------------------------------------------------------------------- /src/Slider.d.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue'; 2 | 3 | interface SliderProps { 4 | modelValue?: any; 5 | value?: any; 6 | id?: string; 7 | disabled?: boolean; 8 | min?: number; 9 | max?: number; 10 | step?: number; 11 | orientation?: 'horizontal' | 'vertical'; 12 | direction?: 'ltr' | 'rtl'; 13 | tooltips?: boolean; 14 | options?: object; 15 | merge?: number; 16 | format?: any; 17 | classes?: object; 18 | showTooltip?: 'always' | 'focus' | 'drag'; 19 | tooltipPosition?: null | 'top' | 'bottom' | 'left' | 'right'; 20 | lazy?: boolean; 21 | ariaLabelledby?: string; 22 | aria?: object; 23 | } 24 | 25 | declare class Slider implements ReturnType { 26 | modelValue: SliderProps['modelValue']; 27 | value: SliderProps['value']; 28 | id: SliderProps['id']; 29 | disabled: SliderProps['disabled']; 30 | min: SliderProps['min']; 31 | max: SliderProps['max']; 32 | step: SliderProps['step']; 33 | orientation: SliderProps['orientation']; 34 | direction: SliderProps['direction']; 35 | tooltips: SliderProps['tooltips']; 36 | options: SliderProps['options']; 37 | merge: SliderProps['merge']; 38 | format: SliderProps['format']; 39 | classes: SliderProps['classes']; 40 | showTooltip: SliderProps['showTooltip']; 41 | tooltipPosition: SliderProps['tooltipPosition']; 42 | lazy: SliderProps['lazy']; 43 | ariaLabelledby: SliderProps['ariaLabelledby']; 44 | aria: SliderProps['aria']; 45 | 46 | $props: SliderProps; 47 | 48 | $emit(eventName: 'start', value: any): this | void; 49 | $emit(eventName: 'slide', value: any): this | void; 50 | $emit(eventName: 'drag', value: any): this | void; 51 | $emit(eventName: 'update', value: any): this | void; 52 | $emit(eventName: 'change', value: any): this | void; 53 | $emit(eventName: 'set', value: any): this | void; 54 | $emit(eventName: 'end', value: any): this | void; 55 | } 56 | 57 | export default Slider; -------------------------------------------------------------------------------- /src/Slider.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/composables/useClasses.js: -------------------------------------------------------------------------------- 1 | import { computed, ref, toRefs } from 'vue' 2 | 3 | export default function useClasses (props, context, dependencies) 4 | { 5 | const { 6 | classes: classes_, showTooltip, tooltipPosition, orientation, 7 | } = toRefs(props) 8 | 9 | // ============== COMPUTED ============== 10 | 11 | const classes = computed(() => ({ 12 | target: 'slider-target', 13 | focused: 'slider-focused', 14 | tooltipFocus: 'slider-tooltip-focus', 15 | tooltipDrag: 'slider-tooltip-drag', 16 | ltr: 'slider-ltr', 17 | rtl: 'slider-rtl', 18 | horizontal: 'slider-horizontal', 19 | vertical: 'slider-vertical', 20 | textDirectionRtl: 'slider-txt-dir-rtl', 21 | textDirectionLtr: 'slider-txt-dir-ltr', 22 | base: 'slider-base', 23 | connects: 'slider-connects', 24 | connect: 'slider-connect', 25 | origin: 'slider-origin', 26 | handle: 'slider-handle', 27 | handleLower: 'slider-handle-lower', 28 | handleUpper: 'slider-handle-upper', 29 | touchArea: 'slider-touch-area', 30 | tooltip: 'slider-tooltip', 31 | tooltipTop: 'slider-tooltip-top', 32 | tooltipBottom: 'slider-tooltip-bottom', 33 | tooltipLeft: 'slider-tooltip-left', 34 | tooltipRight: 'slider-tooltip-right', 35 | tooltipHidden: 'slider-tooltip-hidden', 36 | active: 'slider-active', 37 | draggable: 'slider-draggable', 38 | tap: 'slider-state-tap', 39 | drag: 'slider-state-drag', 40 | 41 | // Unimplemented 42 | pips: 'slider-pips', 43 | pipsHorizontal: 'slider-pips-horizontal', 44 | pipsVertical: 'slider-pips-vertical', 45 | marker: 'slider-marker', 46 | markerHorizontal: 'slider-marker-horizontal', 47 | markerVertical: 'slider-marker-vertical', 48 | markerNormal: 'slider-marker-normal', 49 | markerLarge: 'slider-marker-large', 50 | markerSub: 'slider-marker-sub', 51 | value: 'slider-value', 52 | valueHorizontal: 'slider-value-horizontal', 53 | valueVertical: 'slider-value-vertical', 54 | valueNormal: 'slider-value-normal', 55 | valueLarge: 'slider-value-large', 56 | valueSub: 'slider-value-sub', 57 | 58 | ...classes_.value, 59 | })) 60 | 61 | const classList = computed(() => { 62 | const classList = { ...classes.value } 63 | 64 | Object.keys(classList).forEach((className) => { 65 | classList[className] = Array.isArray(classList[className]) ? classList[className].filter(c => c!==null).join(' ') : classList[className] 66 | }) 67 | 68 | if (showTooltip.value !== 'always') { 69 | classList.target += ` ${showTooltip.value === 'drag' ? classList.tooltipDrag : classList.tooltipFocus}` 70 | } 71 | 72 | if (orientation.value === 'horizontal') { 73 | classList.tooltip += tooltipPosition.value === 'bottom' ? ` ${classList.tooltipBottom}` : ` ${classList.tooltipTop}` 74 | } 75 | 76 | if (orientation.value === 'vertical') { 77 | classList.tooltip += tooltipPosition.value === 'right' ? ` ${classList.tooltipRight}` : ` ${classList.tooltipLeft}` 78 | } 79 | 80 | return classList 81 | }) 82 | 83 | return { 84 | classList, 85 | } 86 | } -------------------------------------------------------------------------------- /src/composables/useSlider.js: -------------------------------------------------------------------------------- 1 | import { ref, computed, toRefs, watch, onMounted, onUnmounted, nextTick } from 'vue' 2 | import nouislider from 'nouislider' 3 | import isNullish from './../utils/isNullish' 4 | import arraysEqual from './../utils/arraysEqual' 5 | 6 | export default function useSlider (props, context, dependencies) 7 | { 8 | const { 9 | orientation, direction, tooltips, step, 10 | min, max, merge, id, disabled, options, 11 | classes, format, lazy, ariaLabelledby, 12 | aria, 13 | } = toRefs(props) 14 | 15 | // ============ DEPENDENCIES ============ 16 | 17 | const value = dependencies.value 18 | const initialValue = dependencies.initialValue 19 | const tooltipsFormat = dependencies.tooltipsFormat 20 | const tooltipsMerge = dependencies.tooltipsMerge 21 | const tooltipFormat = dependencies.tooltipFormat 22 | const classList = dependencies.classList 23 | 24 | // ================ DATA ================ 25 | 26 | const slider = ref(null) 27 | 28 | const slider$ = ref(null) 29 | 30 | // no export 31 | const inited = ref(false) 32 | 33 | // ============== COMPUTED ============== 34 | 35 | // no export 36 | const defaultOptions = computed(() => { 37 | let defaultOptions = { 38 | cssPrefix: '', 39 | cssClasses: classList.value, 40 | orientation: orientation.value, 41 | direction: direction.value, 42 | tooltips: tooltips.value ? tooltipsFormat.value : false, 43 | connect: 'lower', 44 | start: isNullish(value.value) ? min.value : value.value, 45 | range: { 46 | 'min': min.value, 47 | 'max': max.value 48 | } 49 | } 50 | 51 | if (step.value > 0) { 52 | defaultOptions.step = step.value 53 | } 54 | 55 | if (Array.isArray(value.value)) { 56 | defaultOptions.connect = true 57 | } 58 | 59 | if ((ariaLabelledby && ariaLabelledby.value) || (aria && Object.keys(aria.value).length)) { 60 | let handles = Array.isArray(value.value) ? value.value : [value.value] 61 | 62 | defaultOptions.handleAttributes = handles.map(h => (Object.assign({}, aria.value, ariaLabelledby && ariaLabelledby.value ? { 63 | 'aria-labelledby': ariaLabelledby.value, 64 | }: {}))) 65 | } 66 | 67 | if (format.value) { 68 | defaultOptions.ariaFormat = tooltipFormat.value 69 | } 70 | 71 | return defaultOptions 72 | }) 73 | 74 | const sliderProps = computed(() => { 75 | let sliderProps = { 76 | id: id && id.value ? id.value : undefined, 77 | } 78 | 79 | if (disabled.value) { 80 | sliderProps.disabled = true 81 | } 82 | 83 | return sliderProps 84 | }) 85 | 86 | const isRange = computed(() => { 87 | return Array.isArray(value.value) 88 | }) 89 | 90 | // =============== METHODS ============== 91 | 92 | const reset = () => { 93 | updateValue(initialValue.value) 94 | } 95 | 96 | // no export 97 | const getSliderValue = () => { 98 | let sliderValue = slider$.value.get() 99 | 100 | return Array.isArray(sliderValue) 101 | ? sliderValue.map(v => parseFloat(v)) 102 | : parseFloat(sliderValue) 103 | } 104 | 105 | const update = (val, triggerChange = true) => { 106 | slider$.value.set(val, triggerChange) 107 | } 108 | 109 | // no export 110 | const updateValue = (val) => { 111 | context.emit('input', val) 112 | context.emit('update:modelValue', val) 113 | context.emit('update', val) 114 | } 115 | 116 | const init = () => { 117 | slider$.value = nouislider.create(slider.value, Object.assign({}, defaultOptions.value, options.value)) 118 | 119 | if (tooltips.value && isRange.value && merge.value >= 0) { 120 | tooltipsMerge(slider.value, merge.value, ' - ') 121 | } 122 | 123 | slider$.value.on('set', () => { 124 | const sliderValue = getSliderValue() 125 | 126 | context.emit('change', sliderValue) 127 | context.emit('set', sliderValue) 128 | 129 | /* istanbul ignore else */ 130 | if (lazy.value) { 131 | updateValue(sliderValue) 132 | } 133 | }) 134 | 135 | slider$.value.on('update', () => { 136 | if (!inited.value) { 137 | return 138 | } 139 | 140 | const sliderValue = getSliderValue() 141 | 142 | if ((isRange.value && arraysEqual(value.value, sliderValue)) || (!isRange.value && value.value == sliderValue)) { 143 | context.emit('update', sliderValue) 144 | // Required because set event is not 145 | // triggered even though it should be 146 | return 147 | } 148 | 149 | if (!lazy.value) { 150 | updateValue(sliderValue) 151 | } 152 | }) 153 | 154 | /* istanbul ignore next */ 155 | slider$.value.on('start', () => { 156 | context.emit('start', getSliderValue()) 157 | }) 158 | 159 | /* istanbul ignore next */ 160 | slider$.value.on('end', () => { 161 | context.emit('end', getSliderValue()) 162 | }) 163 | 164 | /* istanbul ignore next */ 165 | slider$.value.on('slide', () => { 166 | context.emit('slide', getSliderValue()) 167 | }) 168 | 169 | /* istanbul ignore next */ 170 | slider$.value.on('drag', () => { 171 | context.emit('drag', getSliderValue()) 172 | }) 173 | 174 | slider.value.querySelectorAll('[data-handle]').forEach((handle) => { 175 | handle.onblur = () => { 176 | /* istanbul ignore next */ 177 | if (!slider.value) { 178 | return 179 | } 180 | 181 | classList.value.focused.split(' ').forEach((c) => { 182 | slider.value.classList.remove(c) 183 | }) 184 | } 185 | 186 | handle.onfocus = () => { 187 | classList.value.focused.split(' ').forEach((c) => { 188 | slider.value.classList.add(c) 189 | }) 190 | } 191 | }) 192 | 193 | inited.value = true 194 | } 195 | 196 | const destroy = () => { 197 | slider$.value.off() 198 | slider$.value.destroy() 199 | slider$.value = null 200 | } 201 | 202 | const refresh = (n,o) => { 203 | inited.value = false 204 | destroy() 205 | init() 206 | } 207 | 208 | // ================ HOOKS =============== 209 | 210 | onMounted(init) 211 | onUnmounted(destroy) 212 | 213 | // ============== WATCHERS ============== 214 | 215 | watch(isRange, refresh, { immediate: false }) 216 | watch(min, refresh, { immediate: false }) 217 | watch(max, refresh, { immediate: false }) 218 | watch(step, refresh, { immediate: false }) 219 | watch(orientation, refresh, { immediate: false }) 220 | watch(direction, refresh, { immediate: false }) 221 | watch(tooltips, refresh, { immediate: false }) 222 | watch(merge, refresh, { immediate: false }) 223 | watch(format, refresh, { immediate: false, deep: true }) 224 | watch(options, refresh, { immediate: false, deep: true }) 225 | watch(classes, refresh, { immediate: false, deep: true }) 226 | 227 | watch(value, (value, old) => { 228 | // If old was 0, null, undefined, '', false 229 | if (!old) { 230 | return 231 | } 232 | 233 | if ( 234 | // If both old and new has multiple handles 235 | // and the number of handles decreased 236 | (typeof old === 'object' && typeof value === 'object' && value && Object.keys(old) > Object.keys(value)) || 237 | 238 | // If the old had multiple handles but 239 | // if it decreased to single 240 | (typeof old === 'object' && typeof value !== 'object') || 241 | 242 | // Or has no value at all 243 | isNullish(value) 244 | ) { 245 | refresh() 246 | } 247 | }, { immediate: false }) 248 | 249 | watch(value, (newValue) => { 250 | if (isNullish(newValue)) { 251 | update(min.value, false) 252 | return 253 | } 254 | 255 | let sliderValue = getSliderValue() 256 | 257 | // couldn't reproduce 258 | /* istanbul ignore next */ 259 | if (isRange.value && !Array.isArray(sliderValue)) { 260 | sliderValue = [sliderValue] 261 | } 262 | 263 | if ((isRange.value && !arraysEqual(newValue, sliderValue)) || (!isRange.value && newValue != sliderValue)) { 264 | update(newValue, false) 265 | } 266 | }, { deep: true }) 267 | 268 | return { 269 | slider, 270 | slider$, 271 | isRange, 272 | sliderProps, 273 | init, 274 | destroy, 275 | refresh, 276 | update, 277 | reset, 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/composables/useTooltip.js: -------------------------------------------------------------------------------- 1 | import { computed, toRefs } from 'vue' 2 | import wnumb from 'wnumb' 3 | 4 | export default function useTooltip (props, context, dependencies) 5 | { 6 | const { format, step } = toRefs(props) 7 | 8 | // ============ DEPENDENCIES ============ 9 | 10 | const value = dependencies.value 11 | const classList = dependencies.classList 12 | 13 | // ============== COMPUTED ============== 14 | 15 | // no export 16 | const tooltipFormat = computed(() => { 17 | if (!format || !format.value) { 18 | return wnumb({ decimals: step.value >= 0 ? 0 : 2 }) 19 | } 20 | 21 | if (typeof format.value == 'function') { 22 | return { to: format.value } 23 | } 24 | 25 | return wnumb({...format.value}) 26 | }) 27 | 28 | const tooltipsFormat = computed(() => { 29 | return Array.isArray(value.value) ? value.value.map(v => tooltipFormat.value) : tooltipFormat.value 30 | }) 31 | 32 | // =============== METHODS ============== 33 | 34 | /* istanbul ignore next */ 35 | const tooltipsMerge = (slider, threshold, separator) => { 36 | var textIsRtl = getComputedStyle(slider).direction === 'rtl' 37 | var isRtl = slider.noUiSlider.options.direction === 'rtl' 38 | var isVertical = slider.noUiSlider.options.orientation === 'vertical' 39 | var tooltips = slider.noUiSlider.getTooltips() 40 | var origins = slider.noUiSlider.getOrigins() 41 | 42 | // Move tooltips into the origin element. The default stylesheet handles this. 43 | tooltips.forEach(function (tooltip, index) { 44 | if (tooltip) { 45 | origins[index].appendChild(tooltip) 46 | } 47 | }) 48 | 49 | slider.noUiSlider.on('update', function (values, handle, unencoded, tap, positions) { 50 | var pools = [[]] 51 | var poolPositions = [[]] 52 | var poolValues = [[]] 53 | var atPool = 0 54 | 55 | // Assign the first tooltip to the first pool, if the tooltip is configured 56 | if (tooltips[0]) { 57 | pools[0][0] = 0 58 | poolPositions[0][0] = positions[0] 59 | poolValues[0][0] = tooltipFormat.value.to(parseFloat(values[0])) 60 | } 61 | 62 | for (var i = 1; i < values.length; i++) { 63 | if (!tooltips[i] || (values[i] - values[i - 1]) > threshold) { 64 | atPool++ 65 | pools[atPool] = [] 66 | poolValues[atPool] = [] 67 | poolPositions[atPool] = [] 68 | } 69 | 70 | if (tooltips[i]) { 71 | pools[atPool].push(i) 72 | poolValues[atPool].push(tooltipFormat.value.to(parseFloat(values[i]))) 73 | poolPositions[atPool].push(positions[i]) 74 | } 75 | } 76 | 77 | pools.forEach(function (pool, poolIndex) { 78 | var handlesInPool = pool.length 79 | 80 | for (var j = 0; j < handlesInPool; j++) { 81 | var handleNumber = pool[j] 82 | 83 | if (j === handlesInPool - 1) { 84 | var offset = 0 85 | 86 | poolPositions[poolIndex].forEach(function (value) { 87 | offset += 1000 - value 88 | }) 89 | 90 | var direction = isVertical ? 'bottom' : 'right' 91 | var last = isRtl ? 0 : handlesInPool - 1 92 | var lastOffset = 1000 - poolPositions[poolIndex][last] 93 | offset = (textIsRtl && !isVertical ? 100 : 0) + (offset / handlesInPool) - lastOffset 94 | 95 | // Center this tooltip over the affected handles 96 | tooltips[handleNumber].innerHTML = poolValues[poolIndex].join(separator) 97 | tooltips[handleNumber].style.display = 'block' 98 | tooltips[handleNumber].style[direction] = offset + '%' 99 | 100 | classList.value.tooltipHidden.split(' ').forEach((c) => { 101 | if (tooltips[handleNumber].classList.contains(c)) { 102 | tooltips[handleNumber].classList.remove(c) 103 | } 104 | }) 105 | 106 | } else { 107 | // Hide this tooltip 108 | tooltips[handleNumber].style.display = 'none' 109 | classList.value.tooltipHidden.split(' ').forEach((c) => { 110 | tooltips[handleNumber].classList.add(c) 111 | }) 112 | } 113 | } 114 | }) 115 | }) 116 | } 117 | 118 | return { 119 | tooltipFormat, 120 | tooltipsFormat, 121 | tooltipsMerge, 122 | } 123 | } -------------------------------------------------------------------------------- /src/composables/useValue.js: -------------------------------------------------------------------------------- 1 | import { ref, toRefs } from 'vue' 2 | import isNullish from './../utils/isNullish' 3 | 4 | export default function useValue (props, context, dependencies) 5 | { 6 | const { value: baseValue, modelValue, min } = toRefs(props) 7 | 8 | // ================ DATA ================ 9 | 10 | /* istanbul ignore next */ 11 | let value = modelValue && modelValue.value !== undefined ? modelValue : baseValue 12 | 13 | const initialValue = ref(value.value) 14 | 15 | // ================ HOOKS =============== 16 | 17 | if (isNullish(value.value)) { 18 | value = ref(min.value) 19 | } 20 | 21 | if (Array.isArray(value.value) && value.value.length == 0) { 22 | throw new Error('Slider v-model must not be an empty array') 23 | } 24 | 25 | return { 26 | value, 27 | initialValue, 28 | } 29 | } -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as Slider from './Slider'; 2 | -------------------------------------------------------------------------------- /src/utils/arraysEqual.js: -------------------------------------------------------------------------------- 1 | export default function arraysEqual (array1, array2) { 2 | // couldn't reproduce 3 | /* istanbul ignore next */ 4 | if (!Array.isArray(array1) || !Array.isArray(array2)) { 5 | return false 6 | } 7 | 8 | const array2Sorted = array2.slice().sort() 9 | 10 | return array1.length === array2.length && array1.slice().sort().every(function(value, index) { 11 | return value === array2Sorted[index]; 12 | }) 13 | } -------------------------------------------------------------------------------- /src/utils/isNullish.js: -------------------------------------------------------------------------------- 1 | export default function isNullish (val) { 2 | return [null, undefined, false].indexOf(val) !== -1 3 | } -------------------------------------------------------------------------------- /tailwind.js: -------------------------------------------------------------------------------- 1 | const plugin = require('tailwindcss/plugin') 2 | 3 | module.exports = plugin(({ theme, addBase, addVariant, addUtilities, e }) => { 4 | const plain = { 5 | '.slider-txt-rtl': {}, 6 | '.cursor-grab': { 7 | cursor: 'grab', 8 | }, 9 | '.cursor-grabbing': { 10 | cursor: 'grabbing', 11 | }, 12 | '.touch-none': { 13 | touchAction: 'none', 14 | }, 15 | '.tap-highlight-transparent': { 16 | '-webkit-tap-highlight-color': 'rgba(0,0,0,0)', 17 | }, 18 | '.touch-callout-none': { 19 | '-webkit-touch-callout': 'none', 20 | }, 21 | '.will-change-transform': { 22 | willChange: 'transform', 23 | }, 24 | '.transform-origin-0': { 25 | transformOrigin: '0 0', 26 | }, 27 | '.transform-style-flat': { 28 | '-webkit-transform-style': 'preserve-3d', 29 | transformStyle: 'flat', 30 | }, 31 | '.cursor-inherit': { 32 | cursor: 'inherit', 33 | }, 34 | '.cursor-ew-resize': { 35 | cursor: 'ew-resize', 36 | }, 37 | [`.slider-vertical .${e('v:cursor-ns-resize')}`]: { 38 | cursor: 'ns-resize', 39 | }, 40 | } 41 | 42 | const h = { 43 | '.arrow-bottom': { 44 | '&:before': { 45 | content: '""', 46 | position: 'absolute', 47 | bottom: '-10px', 48 | left: '50%', 49 | width: '0', 50 | height: '0', 51 | border: '5px solid transparent', 52 | borderTopColor: 'inherit', 53 | transform: 'translate(-50%)', 54 | } 55 | }, 56 | '.arrow-top': { 57 | '&:before': { 58 | content: '""', 59 | position: 'absolute', 60 | top: '-10px', 61 | left: '50%', 62 | width: '0', 63 | height: '0', 64 | border: '5px solid transparent', 65 | borderBottomColor: 'inherit', 66 | transform: 'translate(-50%)', 67 | } 68 | }, 69 | } 70 | 71 | const v = { 72 | '.arrow-left': { 73 | '&:before': { 74 | content: '""', 75 | position: 'absolute', 76 | left: '-10px', 77 | top: '50%', 78 | width: '0', 79 | height: '0', 80 | border: '5px solid transparent', 81 | borderRightColor: 'inherit', 82 | transform: 'translateY(-50%)', 83 | } 84 | }, 85 | '.arrow-right': { 86 | '&:before': { 87 | content: '""', 88 | position: 'absolute', 89 | right: '-10px', 90 | top: '50%', 91 | width: '0', 92 | height: '0', 93 | border: '5px solid transparent', 94 | borderLeftColor: 'inherit', 95 | transform: 'translateY(-50%)', 96 | } 97 | }, 98 | } 99 | 100 | addUtilities(plain) 101 | addUtilities(h, ['h']) 102 | addUtilities(v, ['v']) 103 | 104 | addVariant('h', ({ modifySelectors, separator }) => { 105 | modifySelectors(({ className }) => { 106 | return `.slider-horizontal .${e(`h${separator}${className}`)}` 107 | }) 108 | }) 109 | 110 | addVariant('v', ({ modifySelectors, separator }) => { 111 | modifySelectors(({ className }) => { 112 | return `.slider-vertical .${e(`v${separator}${className}`)}` 113 | }) 114 | }) 115 | 116 | addVariant('merge-h', ({ modifySelectors, separator }) => { 117 | modifySelectors(({ className }) => { 118 | return `.slider-horizontal .slider-origin > .${e(`merge-h${separator}${className}`)}` 119 | }) 120 | }) 121 | 122 | addVariant('merge-v', ({ modifySelectors, separator }) => { 123 | modifySelectors(({ className }) => { 124 | return `.slider-vertical .slider-origin > .${e(`merge-v${separator}${className}`)}` 125 | }) 126 | }) 127 | 128 | addVariant('txt-rtl-h', ({ modifySelectors, separator }) => { 129 | modifySelectors(({ className }) => { 130 | return `.slider-horizontal.slider-txt-rtl .${e(`txt-rtl-h${separator}${className}`)}` 131 | }) 132 | }) 133 | 134 | addVariant('tap', ({ modifySelectors, separator }) => { 135 | modifySelectors(({ className }) => { 136 | return `.slider-tap .${e(`tap${separator}${className}`)}` 137 | }) 138 | }) 139 | 140 | addVariant('slider-disabled', ({ modifySelectors, separator }) => { 141 | modifySelectors(({ className }) => { 142 | return `[disabled] .${e(`slider-disabled${separator}${className}`)}` 143 | }) 144 | }) 145 | 146 | addVariant('tt-focus', ({ container, separator }) => { 147 | container.walkRules(rule => { 148 | rule.selector = `.slider-tooltip-focus:not(.slider-focused) .${e(`tt-focus${separator}${rule.selector.slice(1)}`)}` 149 | rule.walkDecls(decl => { 150 | decl.important = true 151 | }) 152 | }) 153 | }) 154 | 155 | addVariant('tt-focused', ({ container, separator }) => { 156 | container.walkRules(rule => { 157 | rule.selector = `.slider-tooltip-focus.slider-focused:not(.slider-tooltip-hidden) .${e(`tt-focused${separator}${rule.selector.slice(1)}`)}` 158 | rule.walkDecls(decl => { 159 | decl.important = true 160 | }) 161 | }) 162 | }) 163 | 164 | addVariant('tt-drag', ({ container, separator }) => { 165 | container.walkRules(rule => { 166 | rule.selector = `.slider-tooltip-drag:not(.slider-state-drag) .${e(`tt-drag${separator}${rule.selector.slice(1)}`)}` 167 | rule.walkDecls(decl => { 168 | decl.important = true 169 | }) 170 | }) 171 | }) 172 | 173 | addVariant('tt-dragging', ({ container, separator }) => { 174 | container.walkRules(rule => { 175 | rule.selector = `.slider-tooltip-drag.slider-state-drag .${e(`tt-dragging${separator}${rule.selector.slice(1)}:not(.slider-tooltip-hidden)`)}, 176 | .slider-tooltip-drag .slider-active .${e(`tt-dragging${separator}${rule.selector.slice(1)}`)}` 177 | rule.walkDecls(decl => { 178 | decl.important = true 179 | }) 180 | }) 181 | }) 182 | }, { 183 | variants: { 184 | extend: { 185 | backgroundColor: ['disabled'], 186 | cursor: ['disabled'], 187 | borderColor: ['disabled'], 188 | height: ['h', 'v'], 189 | width: ['h', 'v'], 190 | inset: ['h', 'v', 'txt-rtl-h', 'merge-h', 'merge-v'], 191 | translate: ['h', 'v', 'merge-h', 'merge-v'], 192 | transitionProperty: ['tap'], 193 | transitionDuration: ['tap'], 194 | display: ['tt-focus', 'tt-focused', 'tt-drag', 'tt-dragging'] 195 | } 196 | }, 197 | theme: { 198 | extend: { 199 | zIndex: { 200 | 1: 1, 201 | }, 202 | width: { 203 | '1\/10': '10%', 204 | }, 205 | height: { 206 | '1\/10': '10%', 207 | }, 208 | minWidth: { 209 | 5: '1.25rem', 210 | }, 211 | inset: { 212 | '-1.25': '-0.3125rem' 213 | }, 214 | boxShadow: { 215 | slider: '0.5px 0.5px 2px 1px rgba(0,0,0,.32)', 216 | 'slider-active': '0.5px 0.5px 2px 1px rgba(0,0,0,.42)', 217 | } 218 | } 219 | } 220 | }) -------------------------------------------------------------------------------- /tests/unit/Slider.spec.js: -------------------------------------------------------------------------------- 1 | import { createSlider } from 'unit-test-helpers' 2 | 3 | describe('Slider', () => { 4 | describe('id', () => { 5 | it('should add id attribute to container DOM', async () => { 6 | const slider = createSlider({ 7 | value: 0, 8 | id: 'my-slider' 9 | }) 10 | 11 | expect(slider.attributes('id')).toBe('my-slider') 12 | }) 13 | }) 14 | }) -------------------------------------------------------------------------------- /tests/unit/composables/useClasses.spec.js: -------------------------------------------------------------------------------- 1 | import { createSlider, getValue, destroy, findAllComponents, findAll, setProp } from 'unit-test-helpers' 2 | import { nextTick } from 'vue' 3 | 4 | describe('useClasses', () => { 5 | describe('classList', () => { 6 | it('should add tooltipFocus to target when showTooltip=focus', () => { 7 | const slider = createSlider({ 8 | value: 5, 9 | showTooltip: 'focus' 10 | }) 11 | 12 | expect(slider.vm.slider.classList.contains(slider.vm.classList.tooltipFocus)).toBe(true) 13 | }) 14 | 15 | it('should add tooltipDrag to target when showTooltip=drag', () => { 16 | const slider = createSlider({ 17 | value: 5, 18 | showTooltip: 'drag' 19 | }) 20 | 21 | expect(slider.vm.slider.classList.contains(slider.vm.classList.tooltipDrag)).toBe(true) 22 | }) 23 | 24 | it('should add tooltipTop when orientation=horizontal', () => { 25 | const slider = createSlider({ 26 | value: 5, 27 | orientation: 'horizontal' 28 | }) 29 | 30 | expect(slider.vm.classList.tooltip.indexOf(slider.vm.classList.tooltipTop) !== -1).toBe(true) 31 | }) 32 | 33 | it('should add tooltipBottom when orientation=horizontal & tooltipPosition=bottom', () => { 34 | const slider = createSlider({ 35 | value: 5, 36 | orientation: 'horizontal', 37 | tooltipPosition: 'bottom' 38 | }) 39 | 40 | expect(slider.vm.classList.tooltip.indexOf(slider.vm.classList.tooltipBottom) !== -1).toBe(true) 41 | }) 42 | 43 | it('should add tooltipTop when orientation=horizontal & tooltipPosition=left', () => { 44 | const slider = createSlider({ 45 | value: 5, 46 | orientation: 'horizontal', 47 | tooltipPosition: 'left' 48 | }) 49 | 50 | expect(slider.vm.classList.tooltip.indexOf(slider.vm.classList.tooltipTop) !== -1).toBe(true) 51 | }) 52 | 53 | it('should add tooltipLeft when orientation=vertical', () => { 54 | const slider = createSlider({ 55 | value: 5, 56 | orientation: 'vertical' 57 | }) 58 | 59 | expect(slider.vm.classList.tooltip.indexOf(slider.vm.classList.tooltipLeft) !== -1).toBe(true) 60 | }) 61 | 62 | it('should add tooltipRight when orientation=vertical & tooltipPosition=right', () => { 63 | const slider = createSlider({ 64 | value: 5, 65 | orientation: 'vertical', 66 | tooltipPosition: 'right' 67 | }) 68 | 69 | expect(slider.vm.classList.tooltip.indexOf(slider.vm.classList.tooltipRight) !== -1).toBe(true) 70 | }) 71 | 72 | it('should add tooltipLeft when orientation=vertical & tooltipPosition=top', () => { 73 | const slider = createSlider({ 74 | value: 5, 75 | orientation: 'vertical', 76 | tooltipPosition: 'top' 77 | }) 78 | 79 | expect(slider.vm.classList.tooltip.indexOf(slider.vm.classList.tooltipLeft) !== -1).toBe(true) 80 | }) 81 | 82 | it('should convert an array of classes to string', () => { 83 | const slider = createSlider({ 84 | value: 5, 85 | classes: { 86 | target: ['slider-target', 'slider-target-2'] 87 | } 88 | }) 89 | 90 | expect(slider.vm.classList.target).toBe('slider-target slider-target-2') 91 | }) 92 | }) 93 | }) -------------------------------------------------------------------------------- /tests/unit/composables/useSlider.spec.js: -------------------------------------------------------------------------------- 1 | import { createSlider, getValue, destroy, findAllComponents, findAll, setProp } from 'unit-test-helpers' 2 | import { nextTick } from 'vue' 3 | 4 | describe('useSlider', () => { 5 | describe('slider', () => { 6 | it('should be slider DOM', () => { 7 | const slider = createSlider({ 8 | value: 5 9 | }) 10 | 11 | expect(slider.vm.slider.classList.contains('slider-target')).toBe(true) 12 | }) 13 | }) 14 | 15 | describe('slider$', () => { 16 | it('should be slider instance', () => { 17 | const slider = createSlider({ 18 | value: 5 19 | }) 20 | 21 | expect(slider.vm.slider$.target).toStrictEqual(slider.vm.slider) 22 | }) 23 | }) 24 | 25 | describe('sliderProps', () => { 26 | it('should include disabled', () => { 27 | const slider = createSlider({ 28 | value: 5, 29 | disabled: true, 30 | }) 31 | 32 | expect(slider.vm.sliderProps).toStrictEqual({ 33 | id: slider.vm.id, 34 | disabled: true, 35 | }) 36 | }) 37 | }) 38 | 39 | describe('isRange', () => { 40 | it('should be false if not range', () => { 41 | const slider = createSlider({ 42 | value: 5 43 | }) 44 | 45 | expect(slider.vm.isRange).toBe(false) 46 | }) 47 | 48 | it('should be true if range', () => { 49 | const slider = createSlider({ 50 | value: [5, 10] 51 | }) 52 | 53 | expect(slider.vm.isRange).toBe(true) 54 | }) 55 | }) 56 | 57 | describe('reset', () => { 58 | it('should reset to initial value when not range', () => { 59 | const slider = createSlider({ 60 | value: 5 61 | }) 62 | 63 | slider.vm.slider$.set(20) 64 | 65 | slider.vm.reset() 66 | 67 | expect(getValue(slider)).toBe(5) 68 | }) 69 | 70 | it('should reset to initial value when range', () => { 71 | const slider = createSlider({ 72 | value: [5, 10, 15] 73 | }) 74 | 75 | slider.vm.slider$.set([20, 25, 30]) 76 | 77 | slider.vm.reset() 78 | 79 | expect(getValue(slider)).toStrictEqual([5, 10, 15]) 80 | }) 81 | }) 82 | 83 | describe('update', () => { 84 | it('should update when not range', async () => { 85 | const slider = createSlider({ 86 | value: 5 87 | }) 88 | 89 | slider.vm.update(20) 90 | 91 | await nextTick() 92 | 93 | expect(getValue(slider)).toBe(20) 94 | }) 95 | 96 | it('should update when range', async () => { 97 | const slider = createSlider({ 98 | value: [5, 10, 15] 99 | }) 100 | 101 | slider.vm.update([20, 25, 30]) 102 | 103 | await nextTick() 104 | 105 | expect(getValue(slider)).toStrictEqual([20, 25, 30]) 106 | }) 107 | }) 108 | 109 | describe('init', () => { 110 | it('should init slider with options', async () => { 111 | const slider = createSlider({ 112 | value: 5, 113 | min: 10, 114 | max: 200, 115 | step: 10, 116 | orientation: 'vertical', 117 | direction: 'rtl', 118 | tooltips: false, 119 | options: { 120 | margin: 20 121 | }, 122 | ariaLabelledby: 'label', 123 | }) 124 | 125 | expect(slider.vm.slider$.target).toStrictEqual(slider.vm.slider) 126 | expect(slider.vm.slider$.options.range.min).toBe(10) 127 | expect(slider.vm.slider$.options.range.max).toBe(200) 128 | expect(slider.vm.slider$.options.step).toBe(10) 129 | expect(slider.vm.slider$.options.orientation).toBe('vertical') 130 | expect(slider.vm.slider$.options.direction).toBe('rtl') 131 | expect(slider.vm.slider$.options.tooltips).toBe(false) 132 | expect(slider.vm.slider$.options.margin).toBe(20) 133 | expect(slider.vm.slider$.options.connect).toBe('lower') 134 | expect(slider.vm.slider$.options.handleAttributes).toEqual([{ 'aria-labelledby': 'label' }]) 135 | }) 136 | 137 | it('should init aria attrs with multiple handles', async () => { 138 | const slider = createSlider({ 139 | value: [5,10], 140 | aria: { 141 | 'aria-invalid': false 142 | } 143 | }) 144 | 145 | expect(slider.vm.slider$.options.handleAttributes).toEqual([{ 'aria-invalid': false },{ 'aria-invalid': false }]) 146 | }) 147 | 148 | it('should emit change on slider set event', async () => { 149 | const slider = createSlider({ 150 | value: 5 151 | }) 152 | 153 | slider.vm.slider$.set(20) 154 | 155 | expect(slider.emitted('change')[0][0]).toBe(20) 156 | }) 157 | 158 | it('should emit change external value set event if lazy', async () => { 159 | const slider = createSlider({ 160 | value: 5 161 | }) 162 | 163 | slider.vm.slider$.set(20) 164 | 165 | expect(slider.emitted('change')[0][0]).toBe(20) 166 | await nextTick() 167 | expect(getValue(slider)).toBe(20) 168 | }) 169 | 170 | it('should emit update on slider update event if not lazy', async () => { 171 | const slider = createSlider({ 172 | value: 5, 173 | lazy: false 174 | }) 175 | 176 | slider.vm.slider$.set(20, false) 177 | 178 | expect(slider.emitted('update')[0][0]).toBe(20) 179 | await nextTick() 180 | expect(getValue(slider)).toBe(20) 181 | }) 182 | 183 | it('should not emit update on slider update event if lazy', async () => { 184 | const slider = createSlider({ 185 | value: 5, 186 | }) 187 | 188 | slider.vm.slider$.set(20, false) 189 | 190 | expect(slider.emitted('update')).toBeFalsy() 191 | await nextTick() 192 | expect(getValue(slider)).toBe(5) 193 | }) 194 | 195 | it('should emit update on slider update event if value has not changed', async () => { 196 | const slider = createSlider({ 197 | value: 5, 198 | }) 199 | 200 | slider.vm.slider$.set(5, false) 201 | 202 | expect(slider.emitted('update')[0][0]).toBe(5) 203 | await nextTick() 204 | expect(getValue(slider)).toBe(5) 205 | }) 206 | 207 | it('should emit update on slider update event if value has not changed and its an array', async () => { 208 | const slider = createSlider({ 209 | value: [5, 10], 210 | }) 211 | 212 | slider.vm.slider$.set([5, 10], false) 213 | 214 | expect(slider.emitted('update')[0][0]).toStrictEqual([5, 10]) 215 | await nextTick() 216 | expect(getValue(slider)).toStrictEqual([5, 10]) 217 | }) 218 | 219 | it('should emit update on slider update event if value has changed and its an array', async () => { 220 | const slider = createSlider({ 221 | value: [5, 10], 222 | }) 223 | 224 | slider.vm.slider$.set([5, 10], false) 225 | 226 | expect(slider.emitted('update')[0][0]).toStrictEqual([5, 10]) 227 | await nextTick() 228 | expect(getValue(slider)).toStrictEqual([5, 10]) 229 | }) 230 | 231 | it('should add focused class on handle focus', async () => { 232 | const slider = createSlider({ 233 | value: 5, 234 | }, { 235 | attach: true 236 | }) 237 | 238 | slider.vm.slider.querySelector('[data-handle]').blur() 239 | slider.vm.slider.querySelector('[data-handle]').focus() 240 | 241 | await nextTick() 242 | 243 | expect(slider.vm.slider.classList.contains(slider.vm.classList.focused)).toBe(true) 244 | }) 245 | 246 | it('should remove focused class on handle blue', async () => { 247 | const slider = createSlider({ 248 | value: 5 249 | }, { 250 | attach: true 251 | }) 252 | 253 | slider.vm.slider.querySelector('[data-handle]').focus() 254 | slider.vm.slider.querySelector('[data-handle]').blur() 255 | 256 | await nextTick() 257 | 258 | expect(slider.vm.slider.classList.contains(slider.vm.classList.focused)).toBe(false) 259 | }) 260 | }) 261 | 262 | describe('destroy', () => { 263 | it('should destroy slider', async () => { 264 | const slider = createSlider({ 265 | value: 5, 266 | }) 267 | 268 | slider.vm.destroy() 269 | 270 | expect(slider.vm.slider$).toBe(null) 271 | expect(slider.vm.slider.classList.contains('slider-target')).toBe(false) 272 | }) 273 | }) 274 | 275 | describe('refresh', () => { 276 | it('should refresh slider', async () => { 277 | const slider = createSlider({ 278 | value: 5, 279 | }) 280 | 281 | slider.vm.slider$.options.connect = 'upper' 282 | 283 | slider.vm.refresh() 284 | 285 | expect(slider.vm.slider$).not.toBe(null) 286 | expect(slider.vm.slider.classList.contains('slider-target')).toBe(true) 287 | expect(slider.vm.slider$.options.connect).toBe('lower') 288 | }) 289 | }) 290 | 291 | describe('onUnmounted', () => { 292 | it('should destroy on unmounted', async () => { 293 | const wrapper = createSlider({ 294 | value: 5, 295 | }, { 296 | returnRoot: true 297 | }) 298 | 299 | const slider = findAllComponents(wrapper, { name: 'Slider' }).at(0) 300 | 301 | destroy(wrapper) 302 | 303 | expect(slider.vm.slider$).toBe(null) 304 | }) 305 | }) 306 | 307 | describe('watch', () => { 308 | it('should refresh slider when isRange changes', async () => { 309 | const slider = createSlider({ 310 | value: 5, 311 | }) 312 | 313 | slider.vm.$parent.value = [10, 20] 314 | 315 | await nextTick() 316 | 317 | expect(slider.vm.isRange).toBe(true) 318 | expect(slider.vm.slider$.options.start).toStrictEqual([10, 20]) 319 | }) 320 | 321 | it('should refresh slider when min changes', async () => { 322 | const slider = createSlider({ 323 | value: 5, 324 | }) 325 | 326 | setProp(slider, slider.vm.$parent.props, 'min', 3) 327 | 328 | await nextTick() 329 | 330 | expect(slider.vm.min).toBe(3) 331 | expect(slider.vm.slider$.options.range.min).toStrictEqual(3) 332 | }) 333 | 334 | it('should refresh slider when max changes', async () => { 335 | const slider = createSlider({ 336 | value: 5, 337 | }) 338 | 339 | setProp(slider, slider.vm.$parent.props, 'max', 50) 340 | 341 | await nextTick() 342 | 343 | expect(slider.vm.max).toBe(50) 344 | expect(slider.vm.slider$.options.range.max).toStrictEqual(50) 345 | }) 346 | 347 | it('should refresh slider when step changes', async () => { 348 | const slider = createSlider({ 349 | value: 5, 350 | }) 351 | 352 | setProp(slider, slider.vm.$parent.props, 'step', 5) 353 | 354 | await nextTick() 355 | 356 | expect(slider.vm.step).toBe(5) 357 | expect(slider.vm.slider$.options.step).toStrictEqual(5) 358 | }) 359 | 360 | it('should refresh slider when orientation changes', async () => { 361 | const slider = createSlider({ 362 | value: 5, 363 | }) 364 | 365 | setProp(slider, slider.vm.$parent.props, 'orientation', 'vertical') 366 | 367 | await nextTick() 368 | 369 | expect(slider.vm.orientation).toBe('vertical') 370 | expect(slider.vm.slider$.options.orientation).toStrictEqual('vertical') 371 | }) 372 | 373 | it('should refresh slider when direction changes', async () => { 374 | const slider = createSlider({ 375 | value: 5, 376 | }) 377 | 378 | setProp(slider, slider.vm.$parent.props, 'direction', 'rtl') 379 | 380 | await nextTick() 381 | 382 | expect(slider.vm.direction).toBe('rtl') 383 | expect(slider.vm.slider$.options.direction).toStrictEqual('rtl') 384 | }) 385 | 386 | it('should refresh slider when tooltips changes', async () => { 387 | const slider = createSlider({ 388 | value: 5, 389 | }) 390 | 391 | setProp(slider, slider.vm.$parent.props, 'tooltips', false) 392 | 393 | await nextTick() 394 | 395 | expect(slider.vm.tooltips).toBe(false) 396 | expect(slider.vm.slider$.options.tooltips).toStrictEqual(false) 397 | }) 398 | 399 | it('should refresh slider when format changes', async () => { 400 | const slider = createSlider({ 401 | value: 5, 402 | format: { } 403 | }) 404 | 405 | setProp(slider, slider.vm.$parent.props.format, 'decimals', 2) 406 | 407 | await nextTick() 408 | 409 | const tooltip = findAll(slider, `.slider-tooltip`).at(0) 410 | 411 | expect(tooltip.html()).toContain('5.00') 412 | }) 413 | 414 | it('should refresh slider when merge changes', async () => { 415 | const slider = createSlider({ 416 | value: [5, 10], 417 | }) 418 | 419 | setProp(slider, slider.vm.$parent.props, 'merge', 10) 420 | 421 | await nextTick() 422 | 423 | const tooltip = findAll(slider, `.slider-tooltip`).at(1) 424 | 425 | expect(tooltip.html()).toContain('5 - 10') 426 | }) 427 | 428 | it('should refresh slider when options changes', async () => { 429 | const slider = createSlider({ 430 | value: 5, 431 | options: {} 432 | }) 433 | 434 | setProp(slider, slider.vm.$parent.props.options, 'margin', 10) 435 | 436 | await nextTick() 437 | 438 | expect(slider.vm.options).toStrictEqual({ margin: 10 }) 439 | expect(slider.vm.slider$.options.margin).toStrictEqual(10) 440 | }) 441 | 442 | it('should refresh slider when the number of handles change', async () => { 443 | const slider = createSlider({ 444 | value: 5, 445 | options: {} 446 | }) 447 | 448 | expect(slider.vm.slider$.get()).toEqual('5.00') 449 | 450 | setProp(slider, slider.vm.$parent, 'value', [10, 20, 30]) 451 | 452 | await nextTick() 453 | 454 | expect(slider.vm.slider$.get()).toEqual(['10.00', '20.00', '30.00']) 455 | 456 | setProp(slider, slider.vm.$parent, 'value', [15, 25]) 457 | 458 | await nextTick() 459 | 460 | expect(slider.vm.slider$.get()).toEqual(['15.00', '25.00']) 461 | 462 | setProp(slider, slider.vm.$parent, 'value', 18) 463 | 464 | await nextTick() 465 | 466 | expect(slider.vm.slider$.get()).toEqual('18.00') 467 | }) 468 | 469 | it('should update slider value if v-model changes when not range and not trigger change', async () => { 470 | let changeMock = jest.fn() 471 | 472 | const slider = createSlider({ 473 | value: 5, 474 | step: -1, 475 | onChange: changeMock, 476 | }) 477 | 478 | expect(slider.vm.slider$.get()).toBe('5.00') 479 | 480 | slider.vm.$parent.value = 10 481 | await nextTick() 482 | expect(slider.vm.slider$.get()).toBe('10.00') 483 | 484 | slider.vm.$parent.value = null 485 | await nextTick() 486 | expect(slider.vm.slider$.get()).toBe('0.00') 487 | 488 | slider.vm.$parent.value = 1.21 489 | await nextTick() 490 | expect(slider.vm.slider$.get()).toBe('1.21') 491 | 492 | slider.vm.$parent.value = undefined 493 | await nextTick() 494 | expect(slider.vm.slider$.get()).toBe('0.00') 495 | 496 | slider.vm.$parent.value = 1 497 | await nextTick() 498 | expect(slider.vm.slider$.get()).toBe('1.00') 499 | 500 | slider.vm.$parent.value = false 501 | await nextTick() 502 | expect(slider.vm.slider$.get()).toBe('0.00') 503 | 504 | expect(changeMock).not.toHaveBeenCalled() 505 | }) 506 | 507 | it('should update slider value if v-model changes when range', async () => { 508 | let changeMock = jest.fn() 509 | 510 | const slider = createSlider({ 511 | value: [5, 10], 512 | step: -1, 513 | onChange: changeMock, 514 | }) 515 | 516 | expect(slider.vm.slider$.get()).toStrictEqual(['5.00', '10.00']) 517 | 518 | slider.vm.$parent.value = [15, 20] 519 | await nextTick() 520 | expect(slider.vm.slider$.get()).toStrictEqual(['15.00', '20.00']) 521 | 522 | slider.vm.$parent.value = null 523 | await nextTick() 524 | expect(slider.vm.slider$.get()).toStrictEqual('0.00') 525 | 526 | slider.vm.$parent.value = [1.21, 2] 527 | await nextTick() 528 | expect(slider.vm.slider$.get()).toStrictEqual(['1.21', '2.00']) 529 | 530 | slider.vm.$parent.value = undefined 531 | await nextTick() 532 | expect(slider.vm.slider$.get()).toStrictEqual('0.00') 533 | 534 | slider.vm.$parent.value = [1, 2] 535 | await nextTick() 536 | expect(slider.vm.slider$.get()).toStrictEqual(['1.00', '2.00']) 537 | 538 | slider.vm.$parent.value = false 539 | await nextTick() 540 | expect(slider.vm.slider$.get()).toStrictEqual('0.00') 541 | 542 | expect(changeMock).not.toHaveBeenCalled() 543 | }) 544 | }) 545 | }) -------------------------------------------------------------------------------- /tests/unit/composables/useTooltip.spec.js: -------------------------------------------------------------------------------- 1 | import { createSlider, getValue, findAll, setProp } from 'unit-test-helpers' 2 | import { nextTick } from 'vue' 3 | 4 | describe('useTooltip', () => { 5 | describe('tooltipsFormat', () => { 6 | it('should have plain number as value & display when not range', async () => { 7 | const slider = createSlider({ 8 | value: 0 9 | }) 10 | 11 | const tooltip = findAll(slider, `.slider-tooltip`).at(0) 12 | 13 | slider.vm.update(5) 14 | 15 | await nextTick() 16 | 17 | expect(getValue(slider)).toBe(5) 18 | expect(tooltip.element.innerHTML).toBe('5') 19 | }) 20 | 21 | it('should have plain number as value & display when range', async () => { 22 | const slider = createSlider({ 23 | value: [0, 1] 24 | }) 25 | const tooltip0 = findAll(slider, `.slider-tooltip`).at(0) 26 | const tooltip1 = findAll(slider, `.slider-tooltip`).at(1) 27 | 28 | slider.vm.update([5,10]) 29 | 30 | await nextTick() 31 | 32 | expect(getValue(slider)).toStrictEqual([5, 10]) 33 | expect(tooltip0.element.innerHTML).toBe('5') 34 | expect(tooltip1.element.innerHTML).toBe('10') 35 | }) 36 | 37 | it('should have float(2) number as value & display when step=-1', async () => { 38 | const slider = createSlider({ 39 | value: 1, 40 | step: -1 41 | }) 42 | 43 | const tooltip = findAll(slider, `.slider-tooltip`).at(0) 44 | 45 | expect(getValue(slider)).toBe(1) 46 | expect(tooltip.element.innerHTML).toBe('1.00') 47 | 48 | slider.vm.update(5.21) 49 | 50 | await nextTick() 51 | 52 | expect(getValue(slider)).toBe(5.21) 53 | expect(tooltip.element.innerHTML).toBe('5.21') 54 | }) 55 | 56 | it('should format display with function', async () => { 57 | const slider = createSlider({ 58 | value: 1, 59 | format: v => '$' + v 60 | }) 61 | 62 | const tooltip = findAll(slider, `.slider-tooltip`).at(0) 63 | 64 | expect(getValue(slider)).toBe(1) 65 | expect(tooltip.element.innerHTML).toBe('$1') 66 | 67 | slider.vm.update(5) 68 | 69 | await nextTick() 70 | 71 | expect(getValue(slider)).toBe(5) 72 | expect(tooltip.element.innerHTML).toBe('$5') 73 | }) 74 | 75 | it('should format display with wNumb object', async () => { 76 | const slider = createSlider({ 77 | value: 1, 78 | format: { 79 | decimals: 2, 80 | prefix: '$' 81 | } 82 | }) 83 | 84 | const tooltip = findAll(slider, `.slider-tooltip`).at(0) 85 | 86 | expect(getValue(slider)).toBe(1) 87 | expect(tooltip.element.innerHTML).toBe('$1.00') 88 | 89 | slider.vm.update(5) 90 | 91 | await nextTick() 92 | 93 | expect(getValue(slider)).toBe(5) 94 | expect(tooltip.element.innerHTML).toBe('$5.00') 95 | }) 96 | }) 97 | 98 | describe('tooltipsMerge', () => { 99 | it('should merge tooltips with default format', async () => { 100 | const slider = createSlider({ 101 | value: [0, 1], 102 | merge: 5, 103 | }) 104 | const tooltip1 = findAll(slider, `.slider-tooltip`).at(1) 105 | 106 | expect(tooltip1.element.innerHTML).toBe('0 - 1') 107 | 108 | slider.vm.update([5,10]) 109 | 110 | await nextTick() 111 | 112 | expect(tooltip1.element.innerHTML).toBe('5 - 10') 113 | }) 114 | 115 | it('should merge tooltips with default format when step=-1', async () => { 116 | const slider = createSlider({ 117 | value: [0, 1], 118 | merge: 10, 119 | step: -1 120 | }) 121 | const tooltip1 = findAll(slider, `.slider-tooltip`).at(1) 122 | 123 | expect(tooltip1.element.innerHTML).toBe('0.00 - 1.00') 124 | 125 | slider.vm.update([5.12,10.21]) 126 | 127 | await nextTick() 128 | 129 | expect(tooltip1.element.innerHTML).toBe('5.12 - 10.21') 130 | }) 131 | 132 | it('should merge tooltips with function format', async () => { 133 | const slider = createSlider({ 134 | value: [0, 1], 135 | merge: 10, 136 | format: v => '$' + v 137 | }) 138 | let tooltip1 = findAll(slider, `.slider-tooltip`).at(1) 139 | 140 | expect(tooltip1.element.innerHTML).toBe('$0 - $1') 141 | 142 | slider.vm.update([5.12,10.21]) 143 | await nextTick() 144 | expect(tooltip1.element.innerHTML).toBe('$5 - $10') 145 | 146 | setProp(slider, slider.vm.$parent.props, 'step', -1) 147 | 148 | await nextTick() 149 | 150 | tooltip1 = findAll(slider, `.slider-tooltip`).at(1) 151 | 152 | slider.vm.update([5.12,10.21]) 153 | await nextTick() 154 | expect(tooltip1.element.innerHTML).toBe('$5.12 - $10.21') 155 | }) 156 | 157 | it('should merge tooltips with wNumb object format', async () => { 158 | const slider = createSlider({ 159 | value: [0, 1], 160 | merge: 10, 161 | format: { 162 | decimals: 2, 163 | prefix: '$' 164 | } 165 | }) 166 | let tooltip1 = findAll(slider, `.slider-tooltip`).at(1) 167 | 168 | expect(tooltip1.element.innerHTML).toBe('$0.00 - $1.00') 169 | 170 | slider.vm.update([5.12,10.21]) 171 | 172 | await nextTick() 173 | 174 | expect(tooltip1.element.innerHTML).toBe('$5.00 - $10.00') 175 | 176 | setProp(slider, slider.vm.$parent.props, 'step', -1) 177 | 178 | await nextTick() 179 | 180 | tooltip1 = findAll(slider, `.slider-tooltip`).at(1) 181 | 182 | slider.vm.update([5.12,10.21]) 183 | await nextTick() 184 | expect(tooltip1.element.innerHTML).toBe('$5.12 - $10.21') 185 | }) 186 | }) 187 | }) -------------------------------------------------------------------------------- /tests/unit/composables/useValue.spec.js: -------------------------------------------------------------------------------- 1 | import { createSlider, getValue } from 'unit-test-helpers' 2 | 3 | describe('useValue', () => { 4 | describe('onCreated', () => { 5 | it('should set v-model as min value is not an array or number', async () => { 6 | const slider = createSlider({ 7 | value: null, 8 | min: 5 9 | }) 10 | 11 | expect(slider.vm.slider$.get()).toBe('5.00') 12 | }) 13 | 14 | it('should throw an error if v-model is an empty array', async () => { 15 | const originalConsoleError = console.error 16 | const originalConsoleWarn = console.warn 17 | console.error = () => {} 18 | console.warn = () => {} 19 | 20 | expect(() => { 21 | createSlider({ 22 | value: [] 23 | }) 24 | }).toThrowError() 25 | 26 | console.error = originalConsoleError 27 | console.warn = originalConsoleWarn 28 | }) 29 | }) 30 | }) -------------------------------------------------------------------------------- /tests/unit/helpers/vue2.js: -------------------------------------------------------------------------------- 1 | import { mount, createLocalVue } from '@vue/test-utils' 2 | import Slider from './../../../src/Slider.vue' 3 | 4 | export const createSlider = (props = {}, options = {}) => { 5 | const localVue = createLocalVue() 6 | 7 | let config = { 8 | localVue, 9 | } 10 | 11 | if (options.attach) { 12 | config.attachTo = document.querySelector('body') 13 | } 14 | 15 | let wrapper = mount({ 16 | data() { 17 | return { 18 | value: props.value, 19 | props: props, 20 | } 21 | }, 22 | template: ` 23 |
24 | 28 |
29 | `, 30 | components: { 31 | Slider, 32 | } 33 | }, config) 34 | 35 | if (options.returnRoot) { 36 | return wrapper 37 | } 38 | 39 | return wrapper.findAllComponents({ name: 'Slider' }).at(0) 40 | } 41 | 42 | export const destroy = (wrapper) => { 43 | wrapper.destroy() 44 | } 45 | 46 | export const findAllComponents = (parent, query) => { 47 | let res = parent.findAllComponents(query) 48 | 49 | return { 50 | at: (i) => { return res.at(i) }, 51 | length: res.length, 52 | } 53 | } 54 | 55 | export const findAll = (parent, query) => { 56 | let res = parent.findAll(query) 57 | 58 | return { 59 | at: (i) => { return res.at(i) }, 60 | length: res.length, 61 | } 62 | } 63 | 64 | export const getValue = (slider) => { 65 | return slider.vm.value 66 | } 67 | 68 | export const setProp = (wrapper, object, prop, value) => { 69 | wrapper.vm.$set(object, prop, value) 70 | } -------------------------------------------------------------------------------- /tests/unit/helpers/vue3.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import Slider from './../../../src/Slider.vue' 3 | 4 | export const createSlider = (props = {}, options = {}) => { 5 | let config = {} 6 | 7 | document.body.innerHTML = ` 8 |
9 |
10 |
11 | ` 12 | 13 | if (options.attach) { 14 | config.attachTo = document.getElementById('app') 15 | } 16 | 17 | let wrapper = mount({ 18 | data() { 19 | return { 20 | value: props.value, 21 | props: props 22 | } 23 | }, 24 | template: ` 25 |
26 | 30 |
31 | `, 32 | components: { 33 | Slider, 34 | } 35 | }, config) 36 | 37 | if (options.returnRoot) { 38 | return wrapper 39 | } 40 | 41 | return wrapper.findAllComponents({ name: 'Slider' })[0] 42 | } 43 | 44 | export const destroy = (wrapper) => { 45 | wrapper.unmount() 46 | } 47 | 48 | export const findAllComponents = (parent, query) => { 49 | let res = parent.findAllComponents(query) 50 | 51 | return { 52 | at: (i) => { return res[i] }, 53 | length: res.length, 54 | } 55 | } 56 | 57 | export const findAll = (parent, query) => { 58 | let res = parent.findAll(query) 59 | 60 | return { 61 | at: (i) => { return res[i] }, 62 | length: res.length, 63 | } 64 | } 65 | 66 | export const getValue = (slider) => { 67 | return slider.vm.modelValue 68 | } 69 | 70 | export const setProp = (wrapper, object, prop, value) => { 71 | object[prop] = value 72 | } -------------------------------------------------------------------------------- /themes/default.css: -------------------------------------------------------------------------------- 1 | .slider-target,.slider-target *{-webkit-touch-callout:none;-webkit-tap-highlight-color:rgba(0,0,0,0);box-sizing:border-box;touch-action:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.slider-target{position:relative}.slider-base,.slider-connects{height:100%;position:relative;width:100%;z-index:1}.slider-connects{overflow:hidden;z-index:0}.slider-connect,.slider-origin{height:100%;position:absolute;right:0;top:0;-ms-transform-origin:0 0;-webkit-transform-origin:0 0;transform-origin:0 0;-webkit-transform-style:preserve-3d;transform-style:flat;width:100%;will-change:transform;z-index:1}.slider-txt-dir-rtl.slider-horizontal .slider-origin{left:0;right:auto}.slider-vertical .slider-origin{top:-100%;width:0}.slider-horizontal .slider-origin{height:0}.slider-handle{-webkit-backface-visibility:hidden;backface-visibility:hidden;position:absolute}.slider-touch-area{height:100%;width:100%}.slider-state-tap .slider-connect,.slider-state-tap .slider-origin{transition:transform .3s}.slider-state-drag *{cursor:inherit!important}.slider-tooltip-drag .slider-tooltip,.slider-tooltip-focus .slider-tooltip{display:none!important}.slider-tooltip-drag .slider-active .slider-tooltip,.slider-tooltip-drag.slider-state-drag .slider-tooltip:not(.slider-tooltip-hidden),.slider-tooltip-focus.slider-focused .slider-tooltip:not(.slider-tooltip-hidden){display:block!important}.slider-horizontal{height:var(--slider-height,6px)}.slider-horizontal .slider-handle{height:var(--slider-handle-height,16px);right:calc(var(--slider-handle-width, 16px)/2*-1);top:calc((var(--slider-handle-height, 16px) - var(--slider-height, 6px))/2*-1 + -1px);width:var(--slider-handle-width,16px)}.slider-vertical{height:var(--slider-vertical-height,300px);width:var(--slider-height,6px)}.slider-vertical .slider-handle{bottom:calc(var(--slider-handle-width, 16px)/2*-1);height:var(--slider-handle-width,16px);right:calc((var(--slider-handle-height, 16px) - var(--slider-height, 6px))/2*-1 + -1px);width:var(--slider-handle-height,16px)}.slider-txt-dir-rtl.slider-horizontal .slider-handle{left:calc(var(--slider-handle-width, 16px)/2*-1);right:auto}.slider-base{background-color:var(--slider-bg,#d1d5db)}.slider-base,.slider-connects{border-radius:var(--slider-radius,9999px)}.slider-connect{background:var(--slider-connect-bg,#10b981);cursor:pointer}.slider-draggable{cursor:ew-resize}.slider-vertical .slider-draggable{cursor:ns-resize}.slider-handle{background:var(--slider-handle-bg,#fff);border:var(--slider-handle-border,0);border-radius:var(--slider-handle-radius,9999px);box-shadow:var(--slider-handle-shadow,.5px .5px 2px 1px rgba(0,0,0,.32));cursor:-webkit-grab;cursor:grab;height:var(--slider-handle-height,16px);width:var(--slider-handle-width,16px)}.slider-handle:focus{box-shadow:0 0 0 var(--slider-handle-ring-width,3px) var(--slider-handle-ring-color,rgba(16,185,129,.188)),var(--slider-handle-shadow,.5px .5px 2px 1px rgba(0,0,0,.32));outline:none}.slider-active{box-shadow:var(--slider-handle-shadow-active,.5px .5px 2px 1px rgba(0,0,0,.42));cursor:-webkit-grabbing;cursor:grabbing}[disabled] .slider-connect{background:var(--slider-connect-bg-disabled,#9ca3af)}[disabled] .slider-handle,[disabled].slider-handle,[disabled].slider-target{cursor:not-allowed}[disabled] .slider-tooltip{background:var(--slider-tooltip-bg-disabled,#9ca3af);border-color:var(--slider-tooltip-bg-disabled,#9ca3af)}.slider-tooltip{background:var(--slider-tooltip-bg,#10b981);border:1px solid var(--slider-tooltip-bg,#10b981);border-radius:var(--slider-tooltip-radius,5px);color:var(--slider-tooltip-color,#fff);display:block;font-size:var(--slider-tooltip-font-size,.875rem);font-weight:var(--slider-tooltip-font-weight,600);line-height:var(--slider-tooltip-line-height,1.25rem);min-width:var(--slider-tooltip-min-width,20px);padding:var(--slider-tooltip-py,2px) var(--slider-tooltip-px,6px);position:absolute;text-align:center;white-space:nowrap}.slider-horizontal .slider-tooltip-top{bottom:calc(var(--slider-handle-height, 16px) + var(--slider-tooltip-arrow-size, 5px) + var(--slider-tooltip-distance, 3px));left:50%;transform:translate(-50%)}.slider-horizontal .slider-tooltip-top:before{border:var(--slider-tooltip-arrow-size,5px) solid transparent;border-top-color:inherit;bottom:calc(var(--slider-tooltip-arrow-size, 5px)*-2);content:"";height:0;left:50%;position:absolute;transform:translate(-50%);width:0}.slider-horizontal .slider-tooltip-bottom{left:50%;top:calc(var(--slider-handle-height, 16px) + var(--slider-tooltip-arrow-size, 5px) + var(--slider-tooltip-distance, 3px));transform:translate(-50%)}.slider-horizontal .slider-tooltip-bottom:before{border:var(--slider-tooltip-arrow-size,5px) solid transparent;border-bottom-color:inherit;content:"";height:0;left:50%;position:absolute;top:calc(var(--slider-tooltip-arrow-size, 5px)*-2);transform:translate(-50%);width:0}.slider-vertical .slider-tooltip-left{right:calc(var(--slider-handle-height, 16px) + var(--slider-tooltip-arrow-size, 5px) + var(--slider-tooltip-distance, 3px));top:50%;transform:translateY(-50%)}.slider-vertical .slider-tooltip-left:before{border:var(--slider-tooltip-arrow-size,5px) solid transparent;border-left-color:inherit;content:"";height:0;position:absolute;right:calc(var(--slider-tooltip-arrow-size, 5px)*-2);top:50%;transform:translateY(-50%);width:0}.slider-vertical .slider-tooltip-right{left:calc(var(--slider-handle-height, 16px) + var(--slider-tooltip-arrow-size, 5px) + var(--slider-tooltip-distance, 3px));top:50%;transform:translateY(-50%)}.slider-vertical .slider-tooltip-right:before{border:var(--slider-tooltip-arrow-size,5px) solid transparent;border-right-color:inherit;content:"";height:0;left:calc(var(--slider-tooltip-arrow-size, 5px)*-2);position:absolute;top:50%;transform:translateY(-50%);width:0}.slider-horizontal .slider-origin>.slider-tooltip{left:auto;transform:translate(50%)}.slider-horizontal .slider-origin>.slider-tooltip-top{bottom:calc(var(--slider-tooltip-arrow-size, 5px) + (var(--slider-handle-height, 16px) - var(--slider-height, 6px))/2 + var(--slider-tooltip-distance, 3px) + 1px)}.slider-horizontal .slider-origin>.slider-tooltip-bottom{top:calc(var(--slider-tooltip-arrow-size, 5px) + (var(--slider-handle-height, 16px) - var(--slider-height, 6px))/2 + var(--slider-tooltip-distance, 3px) + var(--slider-height, 6px) - 1px)}.slider-vertical .slider-origin>.slider-tooltip{top:auto;transform:translateY(calc((var(--slider-tooltip-line-height, 1.25rem) - var(--slider-tooltip-py, 2px))*-1 + 1px))}.slider-vertical .slider-origin>.slider-tooltip-left{right:calc(var(--slider-tooltip-arrow-size, 5px) + var(--slider-height, 6px) + (var(--slider-handle-height, 16px) - var(--slider-height, 6px))/2 + var(--slider-tooltip-distance, 3px) - 1px)}.slider-vertical .slider-origin>.slider-tooltip-right{left:calc(var(--slider-tooltip-arrow-size, 5px) + var(--slider-height, 6px) + (var(--slider-handle-height, 16px) - var(--slider-height, 6px))/2 + var(--slider-tooltip-distance, 3px) - var(--slider-height, 6px) + 1px)} -------------------------------------------------------------------------------- /themes/default.scss: -------------------------------------------------------------------------------- 1 | .slider-target, 2 | .slider-target * { 3 | -webkit-touch-callout: none; 4 | -webkit-tap-highlight-color: rgba(0,0,0,0); 5 | -webkit-user-select: none; 6 | -ms-touch-action: none; 7 | touch-action: none; 8 | -ms-user-select: none; 9 | -moz-user-select: none; 10 | user-select: none; 11 | -moz-box-sizing: border-box; 12 | box-sizing: border-box; 13 | } 14 | 15 | .slider-target { 16 | position: relative; 17 | } 18 | 19 | .slider-base, 20 | .slider-connects { 21 | width: 100%; 22 | height: 100%; 23 | position: relative; 24 | z-index: 1; 25 | } 26 | 27 | .slider-connects { 28 | overflow: hidden; 29 | z-index: 0; 30 | } 31 | 32 | .slider-connect, 33 | .slider-origin { 34 | will-change: transform; 35 | position: absolute; 36 | z-index: 1; 37 | top: 0; 38 | right: 0; 39 | height: 100%; 40 | width: 100%; 41 | -ms-transform-origin: 0 0; 42 | -webkit-transform-origin: 0 0; 43 | -webkit-transform-style: preserve-3d; 44 | transform-origin: 0 0; 45 | transform-style: flat; 46 | } 47 | 48 | .slider-txt-dir-rtl.slider-horizontal .slider-origin { 49 | left: 0; 50 | right: auto; 51 | } 52 | 53 | .slider-vertical .slider-origin { 54 | top: -100%; 55 | width: 0; 56 | } 57 | 58 | .slider-horizontal .slider-origin { 59 | height: 0; 60 | } 61 | 62 | .slider-handle { 63 | -webkit-backface-visibility: hidden; 64 | backface-visibility: hidden; 65 | position: absolute; 66 | } 67 | 68 | .slider-touch-area { 69 | height: 100%; 70 | width: 100%; 71 | } 72 | 73 | .slider-state-tap .slider-connect, 74 | .slider-state-tap .slider-origin { 75 | -webkit-transition: transform 0.3s; 76 | transition: transform 0.3s; 77 | } 78 | 79 | .slider-state-drag * { 80 | cursor: inherit !important; 81 | } 82 | 83 | .slider-tooltip-focus .slider-tooltip, 84 | .slider-tooltip-drag .slider-tooltip { 85 | display: none !important; 86 | } 87 | 88 | .slider-tooltip-focus.slider-focused .slider-tooltip:not(.slider-tooltip-hidden), 89 | .slider-tooltip-drag.slider-state-drag .slider-tooltip:not(.slider-tooltip-hidden), 90 | .slider-tooltip-drag .slider-active .slider-tooltip { 91 | display: block !important; 92 | } 93 | 94 | .slider-horizontal { 95 | height: var(--slider-height, 6px); 96 | } 97 | 98 | .slider-horizontal .slider-handle { 99 | width: var(--slider-handle-width, 16px); 100 | height: var(--slider-handle-height, 16px); 101 | top: calc(((var(--slider-handle-height, 16px) - var(--slider-height, 6px)) / 2 + 1px) * (-1)); 102 | right: calc(var(--slider-handle-width, 16px) / 2 * (-1)); 103 | } 104 | 105 | .slider-vertical { 106 | width: var(--slider-height, 6px); 107 | height: var(--slider-vertical-height, 300px); 108 | } 109 | 110 | .slider-vertical .slider-handle { 111 | width: var(--slider-handle-height, 16px); 112 | height: var(--slider-handle-width, 16px); 113 | right: calc(((var(--slider-handle-height, 16px) - var(--slider-height, 6px)) / 2 + 1px) * (-1)); 114 | bottom: calc(var(--slider-handle-width, 16px) / 2 * (-1)); 115 | } 116 | 117 | .slider-txt-dir-rtl.slider-horizontal .slider-handle { 118 | left: calc(var(--slider-handle-width, 16px) / 2 * (-1)); 119 | right: auto; 120 | } 121 | 122 | .slider-base { 123 | background-color: var(--slider-bg, #D1D5DB); 124 | border-radius: var(--slider-radius, 9999px); 125 | } 126 | 127 | .slider-connects { 128 | border-radius: var(--slider-radius, 9999px); 129 | } 130 | 131 | .slider-connect { 132 | background: var(--slider-connect-bg, #10B981); 133 | cursor: pointer; 134 | } 135 | 136 | .slider-draggable { 137 | cursor: ew-resize; 138 | } 139 | 140 | .slider-vertical .slider-draggable { 141 | cursor: ns-resize; 142 | } 143 | 144 | .slider-handle { 145 | width: var(--slider-handle-width, 16px); 146 | height: var(--slider-handle-height, 16px); 147 | border-radius: var(--slider-handle-radius, 9999px); 148 | background: var(--slider-handle-bg, #fff); 149 | border: var(--slider-handle-border, 0); 150 | box-shadow: var(--slider-handle-shadow, 0.5px 0.5px 2px 1px rgba(0,0,0,.32)); 151 | cursor: grab; 152 | 153 | &:focus { 154 | outline: none; 155 | box-shadow: 0 0 0 var(--slider-handle-ring-width, 3px) var(--slider-handle-ring-color, #10B98130), var(--slider-handle-shadow, 0.5px 0.5px 2px 1px rgba(0,0,0,.32)); 156 | } 157 | } 158 | 159 | .slider-active { 160 | box-shadow: var(--slider-handle-shadow-active, 0.5px 0.5px 2px 1px rgba(0,0,0,.42)); 161 | cursor: grabbing; 162 | } 163 | 164 | [disabled] .slider-connect { 165 | background: var(--slider-connect-bg-disabled, #9CA3AF); 166 | } 167 | 168 | [disabled].slider-target, 169 | [disabled].slider-handle, 170 | [disabled] .slider-handle { 171 | cursor: not-allowed; 172 | } 173 | 174 | [disabled] .slider-tooltip { 175 | background: var(--slider-tooltip-bg-disabled, #9CA3AF); 176 | border-color: var(--slider-tooltip-bg-disabled, #9CA3AF); 177 | } 178 | 179 | .slider-tooltip { 180 | position: absolute; 181 | display: block; 182 | font-size: var(--slider-tooltip-font-size, 0.875rem); 183 | line-height: var(--slider-tooltip-line-height, 1.25rem); 184 | font-weight: var(--slider-tooltip-font-weight, 600); 185 | white-space: nowrap; 186 | padding: var(--slider-tooltip-py, 2px) var(--slider-tooltip-px, 6px); 187 | min-width: var(--slider-tooltip-min-width, 20px); 188 | text-align: center; 189 | color: var(--slider-tooltip-color, #fff); 190 | border-radius: var(--slider-tooltip-radius, 5px); 191 | border: 1px solid var(--slider-tooltip-bg, #10B981); 192 | background: var(--slider-tooltip-bg, #10B981); 193 | } 194 | 195 | .slider-horizontal .slider-tooltip-top { 196 | -webkit-transform: translate(-50%, 0); 197 | transform: translate(-50%, 0); 198 | left: 50%; 199 | bottom: calc(var(--slider-handle-height, 16px) + var(--slider-tooltip-arrow-size, 5px) + var(--slider-tooltip-distance, 3px)); 200 | 201 | &:before { 202 | content: ""; 203 | position: absolute; 204 | bottom: calc(var(--slider-tooltip-arrow-size, 5px) * (-2)); 205 | left: 50%; 206 | width: 0; 207 | height: 0; 208 | border: var(--slider-tooltip-arrow-size, 5px) solid transparent; 209 | border-top-color: inherit; 210 | transform: translate(-50%); 211 | } 212 | } 213 | 214 | .slider-horizontal .slider-tooltip-bottom { 215 | -webkit-transform: translate(-50%, 0); 216 | transform: translate(-50%, 0); 217 | left: 50%; 218 | top: calc(var(--slider-handle-height, 16px) + var(--slider-tooltip-arrow-size, 5px) + var(--slider-tooltip-distance, 3px)); 219 | 220 | &:before { 221 | content: ""; 222 | position: absolute; 223 | top: calc(var(--slider-tooltip-arrow-size, 5px) * (-2)); 224 | left: 50%; 225 | width: 0; 226 | height: 0; 227 | border: var(--slider-tooltip-arrow-size, 5px) solid transparent; 228 | border-bottom-color: inherit; 229 | transform: translate(-50%); 230 | } 231 | } 232 | 233 | .slider-vertical .slider-tooltip-left { 234 | -webkit-transform: translate(0, -50%); 235 | transform: translate(0, -50%); 236 | top: 50%; 237 | right: calc(var(--slider-handle-height, 16px) + var(--slider-tooltip-arrow-size, 5px) + var(--slider-tooltip-distance, 3px)); 238 | 239 | &:before { 240 | content: ""; 241 | position: absolute; 242 | right: calc(var(--slider-tooltip-arrow-size, 5px) * (-2)); 243 | top: 50%; 244 | width: 0; 245 | height: 0; 246 | border: var(--slider-tooltip-arrow-size, 5px) solid transparent; 247 | border-left-color: inherit; 248 | transform: translateY(-50%); 249 | } 250 | } 251 | 252 | .slider-vertical .slider-tooltip-right { 253 | -webkit-transform: translate(0, -50%); 254 | transform: translate(0, -50%); 255 | top: 50%; 256 | left: calc(var(--slider-handle-height, 16px) + var(--slider-tooltip-arrow-size, 5px) + var(--slider-tooltip-distance, 3px)); 257 | 258 | &:before { 259 | content: ""; 260 | position: absolute; 261 | left: calc(var(--slider-tooltip-arrow-size, 5px) * (-2)); 262 | top: 50%; 263 | width: 0; 264 | height: 0; 265 | border: var(--slider-tooltip-arrow-size, 5px) solid transparent; 266 | border-right-color: inherit; 267 | transform: translateY(-50%); 268 | } 269 | } 270 | 271 | .slider-horizontal .slider-origin > .slider-tooltip { 272 | -webkit-transform: translate(50%, 0); 273 | transform: translate(50%, 0); 274 | left: auto; 275 | } 276 | 277 | .slider-horizontal .slider-origin > .slider-tooltip-top { 278 | bottom: calc(var(--slider-tooltip-arrow-size, 5px) + ((var(--slider-handle-height, 16px) - var(--slider-height, 6px)) / 2) + var(--slider-tooltip-distance, 3px) + 1px); 279 | } 280 | 281 | .slider-horizontal .slider-origin > .slider-tooltip-bottom { 282 | top: calc(var(--slider-tooltip-arrow-size, 5px) + ((var(--slider-handle-height, 16px) - var(--slider-height, 6px)) / 2) + var(--slider-tooltip-distance, 3px) + var(--slider-height, 6px) - 1px); 283 | } 284 | 285 | .slider-vertical .slider-origin > .slider-tooltip { 286 | transform: translate(0, calc((var(--slider-tooltip-line-height, 1.25rem) - var(--slider-tooltip-py, 2px)) * (-1) + 1px)); 287 | top: auto; 288 | } 289 | 290 | .slider-vertical .slider-origin > .slider-tooltip-left { 291 | right: calc(var(--slider-tooltip-arrow-size, 5px) + var(--slider-height, 6px) + ((var(--slider-handle-height, 16px) - var(--slider-height, 6px)) / 2) + var(--slider-tooltip-distance, 3px) - 1px); 292 | } 293 | 294 | .slider-vertical .slider-origin > .slider-tooltip-right { 295 | left: calc(var(--slider-tooltip-arrow-size, 5px) + var(--slider-height, 6px) + ((var(--slider-handle-height, 16px) - var(--slider-height, 6px)) / 2) + var(--slider-tooltip-distance, 3px) - var(--slider-height, 6px) + 1px); 296 | } -------------------------------------------------------------------------------- /themes/tailwind.css: -------------------------------------------------------------------------------- 1 | .slider-target { 2 | @apply relative box-border select-none touch-none tap-highlight-transparent touch-callout-none slider-disabled:cursor-not-allowed; 3 | } 4 | 5 | .slider-horizontal { 6 | @apply h-1.5; 7 | } 8 | 9 | .slider-vertical { 10 | @apply w-1.5 h-80; 11 | } 12 | 13 | .slider-txt-dir-rtl { 14 | @apply slider-txt-rtl; 15 | } 16 | 17 | .slider-base { 18 | @apply w-full h-full relative z-1 bg-gray-300 rounded; 19 | } 20 | 21 | .slider-connects { 22 | @apply w-full h-full relative overflow-hidden z-0 rounded; 23 | } 24 | 25 | .slider-connect { 26 | @apply absolute z-1 top-0 right-0 transform-origin-0 transform-style-flat h-full w-full bg-green-500 cursor-pointer tap:duration-300 tap:transition-transform slider-disabled:bg-gray-400 slider-disabled:cursor-not-allowed; 27 | } 28 | 29 | .slider-origin { 30 | @apply absolute z-1 top-0 right-0 transform-origin-0 transform-style-flat h-full w-full h:h-0 v:-top-full txt-rtl-h:left-0 txt-rtl-h:right-auto v:w-0 tap:duration-300 tap:transition-transform; 31 | } 32 | 33 | .slider-handle { 34 | @apply absolute rounded-full bg-white border-0 shadow-slider cursor-grab focus:outline-none h:w-4 h:h-4 h:-top-1.5 h:-right-2 txt-rtl-h:-left-2 txt-rtl-h:right-auto v:w-4 v:h-4 v:-top-2 v:-right-1.25 slider-disabled:cursor-not-allowed focus:ring focus:ring-green-500 focus:ring-opacity-30; 35 | } 36 | 37 | .slider-touch-area { 38 | @apply h-full w-full; 39 | } 40 | 41 | .slider-tooltip { 42 | @apply absolute block text-sm font-semibold whitespace-nowrap py-1 px-1.5 min-w-5 text-center text-white rounded border border-green-500 bg-green-500 transform h:-translate-x-1/2 h:left-1/2 v:-translate-y-1/2 v:top-1/2 slider-disabled:bg-gray-400 slider-disabled:border-gray-400 merge-h:translate-x-1/2 merge-h:left-auto merge-v:-translate-x-4 merge-v:top-auto tt-focus:hidden tt-focused:block tt-drag:hidden tt-dragging:block; 43 | } 44 | 45 | .slider-tooltip-top { 46 | @apply bottom-6 h:arrow-bottom merge-h:bottom-3.5; 47 | } 48 | 49 | .slider-tooltip-bottom { 50 | @apply top-6 h:arrow-top merge-h:top-5; 51 | } 52 | 53 | .slider-tooltip-left { 54 | @apply right-6 v:arrow-right merge-v:right-1; 55 | } 56 | 57 | .slider-tooltip-right { 58 | @apply left-6 v:arrow-left merge-v:left-7; 59 | } 60 | 61 | .slider-active { 62 | @apply shadow-slider-active cursor-grabbing; 63 | } 64 | 65 | .slider-draggable { 66 | @apply cursor-ew-resize v:cursor-ns-resize; 67 | } -------------------------------------------------------------------------------- /themes/tailwind.scss: -------------------------------------------------------------------------------- 1 | .slider-target { 2 | @apply relative box-border select-none touch-none tap-highlight-transparent touch-callout-none slider-disabled:cursor-not-allowed; 3 | } 4 | 5 | .slider-focused { 6 | } 7 | 8 | .slider-tooltip-focus { 9 | } 10 | 11 | .slider-tooltip-drag { 12 | } 13 | 14 | .slider-ltr { 15 | } 16 | 17 | .slider-rtl { 18 | } 19 | 20 | .slider-horizontal { 21 | @apply h-1.5; 22 | } 23 | 24 | .slider-vertical { 25 | @apply w-1.5 h-80; 26 | } 27 | 28 | .slider-txt-dir-rtl { 29 | @apply slider-txt-rtl; 30 | } 31 | 32 | .slider-txt-dir-ltr { 33 | } 34 | 35 | .slider-base { 36 | @apply w-full h-full relative z-1 bg-gray-300 rounded; 37 | } 38 | 39 | .slider-connects { 40 | @apply w-full h-full relative overflow-hidden z-0 rounded; 41 | } 42 | 43 | .slider-connect { 44 | @apply absolute z-1 top-0 right-0 transform-origin-0 transform-style-flat h-full w-full bg-green-500 cursor-pointer tap:duration-300 tap:transition-transform slider-disabled:bg-gray-400 slider-disabled:cursor-not-allowed; 45 | } 46 | 47 | .slider-origin { 48 | @apply absolute z-1 top-0 right-0 transform-origin-0 transform-style-flat h-full w-full h:h-0 v:-top-full txt-rtl-h:left-0 txt-rtl-h:right-auto v:w-0 tap:duration-300 tap:transition-transform; 49 | } 50 | 51 | .slider-handle { 52 | @apply absolute rounded-full bg-white border-0 shadow-slider cursor-grab focus:outline-none h:w-4 h:h-4 h:-top-1.5 h:-right-2 txt-rtl-h:-left-2 txt-rtl-h:right-auto v:w-4 v:h-4 v:-top-2 v:-right-1.25 slider-disabled:cursor-not-allowed focus:ring focus:ring-green-500 focus:ring-opacity-30; 53 | } 54 | 55 | .slider-touch-area { 56 | @apply h-full w-full; 57 | } 58 | 59 | .slider-tooltip { 60 | @apply absolute block text-sm font-semibold whitespace-nowrap py-1 px-1.5 min-w-5 text-center text-white rounded border border-green-500 bg-green-500 transform h:-translate-x-1/2 h:left-1/2 v:-translate-y-1/2 v:top-1/2 slider-disabled:bg-gray-400 slider-disabled:border-gray-400 merge-h:translate-x-1/2 merge-h:left-auto merge-v:-translate-x-4 merge-v:top-auto tt-focus:hidden tt-focused:block tt-drag:hidden tt-dragging:block; 61 | } 62 | 63 | .slider-tooltip-top { 64 | @apply bottom-6 h:arrow-bottom merge-h:bottom-3.5; 65 | } 66 | 67 | .slider-tooltip-bottom { 68 | @apply top-6 h:arrow-top merge-h:top-5; 69 | } 70 | 71 | .slider-tooltip-left { 72 | @apply right-6 v:arrow-right merge-v:right-1; 73 | } 74 | 75 | .slider-tooltip-right { 76 | @apply left-6 v:arrow-left merge-v:left-7; 77 | } 78 | 79 | .slider-tooltip-hidden { 80 | } 81 | 82 | .slider-active { 83 | @apply shadow-slider-active cursor-grabbing; 84 | } 85 | 86 | .slider-draggable { 87 | @apply cursor-ew-resize v:cursor-ns-resize; 88 | } 89 | 90 | .slider-state-tap { 91 | } 92 | 93 | .slider-state-drag { 94 | } 95 | --------------------------------------------------------------------------------