Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dual Range Slider #2224

Closed
Silvallis opened this issue Nov 7, 2023 · 3 comments
Closed

Dual Range Slider #2224

Silvallis opened this issue Nov 7, 2023 · 3 comments
Labels
feature request Request a feature or introduce and update to the project.
Milestone

Comments

@Silvallis
Copy link

Describe the feature in detail (code, mocks, or screenshots encouraged)

A new type of range slider with two pips commonly used for price ranges (for example).
image

This feature could either be an enhancement of the existing RangeSlider component or be a new component.

I found this implementation of a dual range slider in pure HTML/CSS:

<div class="range_container">
    <div class="sliders_control">
        <input id="fromSlider" type="range" value="10" min="0" max="100"/>
        <input id="toSlider" type="range" value="40" min="0" max="100"/>
    </div>
    <div class="form_control">
        <div class="form_control_container">
            <div class="form_control_container__time">Min</div>
            <input class="form_control_container__time__input" type="number" id="fromInput" value="10" min="0" max="100"/>
        </div>
        <div class="form_control_container">
            <div class="form_control_container__time">Max</div>
            <input class="form_control_container__time__input" type="number" id="toInput" value="40" min="0" max="100"/>
        </div>
    </div>
</div>
.range_container {
  display: flex;
  flex-direction: column;
  width: 80%;
  margin: 35% auto;
}

.sliders_control {
  position: relative;
  min-height: 50px;
}

.form_control {
  position: relative;
  display: flex;
  justify-content: space-between;
  font-size: 24px;
  color: #635a5a;
}

input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none;
  pointer-events: all;
  width: 24px;
  height: 24px;
  background-color: #fff;
  border-radius: 50%;
  box-shadow: 0 0 0 1px #C6C6C6;
  cursor: pointer;
}

input[type=range]::-moz-range-thumb {
  -webkit-appearance: none;
  pointer-events: all;
  width: 24px;
  height: 24px;
  background-color: #fff;
  border-radius: 50%;
  box-shadow: 0 0 0 1px #C6C6C6;
  cursor: pointer;  
}

input[type=range]::-webkit-slider-thumb:hover {
  background: #f7f7f7;
}

input[type=range]::-webkit-slider-thumb:active {
  box-shadow: inset 0 0 3px #387bbe, 0 0 9px #387bbe;
  -webkit-box-shadow: inset 0 0 3px #387bbe, 0 0 9px #387bbe;
}

input[type="number"] {
  color: #8a8383;
  width: 50px;
  height: 30px;
  font-size: 20px;
  border: none;
}

input[type=number]::-webkit-inner-spin-button, 
input[type=number]::-webkit-outer-spin-button {  
   opacity: 1;
}

input[type="range"] {
  -webkit-appearance: none; 
  appearance: none;
  height: 2px;
  width: 100%;
  position: absolute;
  background-color: #C6C6C6;
  pointer-events: none;
}

#fromSlider {
  height: 0;
  z-index: 1;
}

image

The biggest problem will be that there is no native option in the range input element, therefore it would need to be a custom option like the one provided.

What type of pull request would this be?

New Feature

Provide relevant links or additional information.

https://w3collective.com/double-range-slider-html-css-js/
https://medium.com/@predragdavidovic10/native-dual-range-slider-html-css-javascript-91e778134816
https://svelte.dev/repl/75d34e46cbe64bb68b7c2ac2c61931ce?version=4.2.2
https://codepen.io/predragdavidovic/pen/mdpMoWo

@Silvallis Silvallis added the feature request Request a feature or introduce and update to the project. label Nov 7, 2023
@Silvallis
Copy link
Author

Forgot to paste the JS portion of the example:

function controlFromInput(fromSlider, fromInput, toInput, controlSlider) {
    const [from, to] = getParsed(fromInput, toInput);
    fillSlider(fromInput, toInput, '#C6C6C6', '#25daa5', controlSlider);
    if (from > to) {
        fromSlider.value = to;
        fromInput.value = to;
    } else {
        fromSlider.value = from;
    }
}
    
function controlToInput(toSlider, fromInput, toInput, controlSlider) {
    const [from, to] = getParsed(fromInput, toInput);
    fillSlider(fromInput, toInput, '#C6C6C6', '#25daa5', controlSlider);
    setToggleAccessible(toInput);
    if (from <= to) {
        toSlider.value = to;
        toInput.value = to;
    } else {
        toInput.value = from;
    }
}

function controlFromSlider(fromSlider, toSlider, fromInput) {
  const [from, to] = getParsed(fromSlider, toSlider);
  fillSlider(fromSlider, toSlider, '#C6C6C6', '#25daa5', toSlider);
  if (from > to) {
    fromSlider.value = to;
    fromInput.value = to;
  } else {
    fromInput.value = from;
  }
}

function controlToSlider(fromSlider, toSlider, toInput) {
  const [from, to] = getParsed(fromSlider, toSlider);
  fillSlider(fromSlider, toSlider, '#C6C6C6', '#25daa5', toSlider);
  setToggleAccessible(toSlider);
  if (from <= to) {
    toSlider.value = to;
    toInput.value = to;
  } else {
    toInput.value = from;
    toSlider.value = from;
  }
}

function getParsed(currentFrom, currentTo) {
  const from = parseInt(currentFrom.value, 10);
  const to = parseInt(currentTo.value, 10);
  return [from, to];
}

function fillSlider(from, to, sliderColor, rangeColor, controlSlider) {
    const rangeDistance = to.max-to.min;
    const fromPosition = from.value - to.min;
    const toPosition = to.value - to.min;
    controlSlider.style.background = `linear-gradient(
      to right,
      ${sliderColor} 0%,
      ${sliderColor} ${(fromPosition)/(rangeDistance)*100}%,
      ${rangeColor} ${((fromPosition)/(rangeDistance))*100}%,
      ${rangeColor} ${(toPosition)/(rangeDistance)*100}%, 
      ${sliderColor} ${(toPosition)/(rangeDistance)*100}%, 
      ${sliderColor} 100%)`;
}

function setToggleAccessible(currentTarget) {
  const toSlider = document.querySelector('#toSlider');
  if (Number(currentTarget.value) <= 0 ) {
    toSlider.style.zIndex = 2;
  } else {
    toSlider.style.zIndex = 0;
  }
}

const fromSlider = document.querySelector('#fromSlider');
const toSlider = document.querySelector('#toSlider');
const fromInput = document.querySelector('#fromInput');
const toInput = document.querySelector('#toInput');
fillSlider(fromSlider, toSlider, '#C6C6C6', '#25daa5', toSlider);
setToggleAccessible(toSlider);

fromSlider.oninput = () => controlFromSlider(fromSlider, toSlider, fromInput);
toSlider.oninput = () => controlToSlider(fromSlider, toSlider, toInput);
fromInput.oninput = () => controlFromInput(fromSlider, fromInput, toInput, toSlider);
toInput.oninput = () => controlToInput(toSlider, fromInput, toInput, toSlider);

@endigo9740
Copy link
Contributor

endigo9740 commented Nov 7, 2023

Hey @Silvallis, yeah we've heard this request on a number of occasions. Unfortunately it's not just a matter of sitting down and building this, but rather the fact this would go against our standards for handling form components. That being, we actively try to avoid creating new form components whenever possible and instead favor native options instead.

In fact, most of our form component elements (ex: Range Slider) are just wrappers around the native implemented features, but with some nice quality of life added. For example, take the Range Slider. We generate the native datalist required to render tick marks:

https://github.com/skeletonlabs/skeleton/blob/master/packages/skeleton/src/lib/components/RangeSlider/RangeSlider.svelte#L98

Our biggest hang up is introducing elements that are not fully accessible. A11y is a major concern of Skeleton, and we would rather go without a feature than implement one with poor support for accessibility. While your example shared above is likely functional, it doesn't appear to make considerations for a11y.

If you or any other volunteers would like to take on the task of building a really first class a11y friendly dual range slider, we would gladly accept it. Otherwise this will likely remain pending for now. Just want to set clear expectations here.

@endigo9740 endigo9740 added this to the v3.0 (Next) milestone Dec 19, 2023
@endigo9740 endigo9740 mentioned this issue Dec 27, 2023
2 tasks
@endigo9740
Copy link
Contributor

In an effort to prepare for Skeleton v3, we're consolidate some related issues down to a single ticket. This will ensure that we can see the full context of requests when the time comes to refactor and update this feature going forward. If you wish to add additional feedback or suggestions, please so here:

@endigo9740 endigo9740 modified the milestones: v3.0 (Next), v2.0 Jan 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request Request a feature or introduce and update to the project.
Projects
None yet
Development

No branches or pull requests

2 participants