#StackBounty: #html #css #flexbox #safari Text selection background becoming invisible in Safari when selecting inside flex elements wi…

Bounty: 300

When I select text across multiple flex element items in Safari, the selection background becomes invisible on some part of the text.

I’m using React 17 but I would be surprised if the problem was from there.

Here’s some screenshots of the difference between Firefox and Safari:

And here’s a simple code sandbox to reproduce: https://codesandbox.io/s/great-glade-60fmz

Did anyone encountered this problem before?


Get this bounty!!!

#StackBounty: #android #html #css #font-size CSS: scale non-text elements based on the system's font size

Bounty: 100

I’m trying to limit an element to 2 lines of text and I wanted a fallback for -webkit-line-clamp. This solution works when the system’s default font size is the default:

line-height: 1.3em;
max-height: 2.6em;
overflow: hidden;

However, on Android, if I change the system’s font size, the bottom of the second line is cut off:

enter image description here

enter image description here

The font-size is 16px, but with Android’s scaling (1.3x), the computed styles are:

font-size: 20.8px;
line-height: 27.04px;
max-height: 41.6px;

So it looks like font-size and line-height are scaled, but not max-height. Since max-height is based on em, I expected it to scale too. Is there a way to make this work?


Get this bounty!!!

#StackBounty: #javascript #html How to ignore disable question in HTML form so i will able to move in next section using "Next&quo…

Bounty: 50

I have created a form in which we are asking users a few questions. There are a few sections and in each section there are 5 to 10 questions. Users can disable the question using the NA button. But they need to answer the remaining question. I am able to create everything but having issues with the Next button. I am not able to move to the next section because of disabled question. It is asking to answer disable question also. Please help here… below is the code…. Also, I want to remove any given answer in case the user clicks the NA button.

$(document).ready(function() {
  let ctr = 1;
  $('.answers').each(function(index) {
    let i = index + 1
    let html = ` <div class="form-check-inline section-1">
    <input class="form-check-input" type="radio" name="question${i}" id="gridRadios${ctr}" value="1">
    <label class="form-check-label" for="gridRadios${ctr}"> Never</label>
    </div>
    <div class="form-check-inline section-1">
    <input class="form-check-input" type="radio" name="question${i}" id="gridRadios${ctr}" value="2">
    <label class="form-check-label" for="gridRadios${ctr}">Rarely</label>
    </div>
    <div class="form-check-inline section-1">
    <input class="form-check-input" type="radio" name="question${i}" id="gridRadios${ctr}" value="3">
    <label class="form-check-label" for="gridRadios${ctr}">Occasionally</label>
    </div>
    <div class="form-check-inline section-1">
    <input class="form-check-input" type="radio" name="question${i}" id="gridRadios${ctr}" value="4">
    <label class="form-check-label" for="gridRadios${ctr}">Often</label>
    </div>
    <div class="form-check-inline section-1">
    <input class="form-check-input" type="radio" name="question${i}" id="gridRadios${ctr}" value="5">
    <label class="form-check-label" for="gridRadios${ctr}">Always</label>
    </div>

    <div class="form-group">
    <input type="button" name="q${i}Remark" value="Remark" onclick="onButtonClick(${i})" />
    <input class="hide" type="text" id="textInput${i}" value="" oninput="updateTextBox()" />
    <p>Remaining Characters: <span id="chars-left">100</span></p>
    </div>
    <div class="form-group">
    <button name="disable${i}" id="na${i}" class='btn-na'>N/A</button>
    </div>
    `
    $(this).html(html);
    ctr++;
  })




  $(".btn-na").click(function() {
    let n = $('.answers.disabled').length
    if (n >= 3) {
      alert('You can only disable 3');
      return
    }
    $(this).closest('.answers').find("input").attr('disabled', true);
    $(this).closest('.answers').addClass('disabled')
  })
});

$('.btnNextS1').click(function() {
  if ($('div.row1:not(:has(:radio:checked))').length) {
    $('div.row1:not(:has(:radio:checked))').parent().after("<div class='validation' style='color:red;margin-bottom: 20px;'>Please Answer this question</div>");
  } else {
    // e.stopPropagation();
    $('ul.nav-tabs li.nav-item     a.active').closest('li').next('li').find('a').trigger('click');
  }
});
<div class="section-1-questions">
  <div style="background-color:greenyellow;"> <b>Section 1:</b> </div><br>
  <fieldset class="form-group">
    <div class="row1">
      <div class="question1">
        <legend id="q1" class="col-form-label col-sm-8 pt-0"><b>1) Question 1</b></legend>
        <div class="col-sm-10 answers">
        </div>
      </div>
    </div>
  </fieldset>
  <fieldset class="form-group">
    <div class="row1">
      <legend id="q2" class="col-form-label col-sm-8 pt-3"><b>2) Question 2</b></legend>
      <div class="col-sm-10 answers">
      </div>
    </div>
  </fieldset>
  <fieldset class="form-group">
    <div class="row1">
      <legend id="q3" class="col-form-label col-sm-8 pt-3"><b>3) Question 3</b></legend>
      <div class="col-sm-10 answers">
      </div>
    </div>
  </fieldset>
  <fieldset class="form-group">
    <div class="row1">
      <legend id="q4" class="col-form-label col-sm-8 pt-3"><b>4) Question 4</b></legend>
      <div class="col-sm-10 answers">
      </div>
    </div>
  </fieldset>
  <fieldset class="form-group">
    <div class="row1">
      <legend id="q5" class="col-form-label col-sm-8 pt-3"><b>5) QUESTION 5</b></legend>
      <div class="col-sm-10 answers">
      </div>
    </div>
  </fieldset>
  <div class="form-group">
    <label for="remarks"><b>Remarks / Observations </b></label>
    <input type="name" class="form-control" name="Remarks1" id="remarks1" aria-describedby="nameHelp" placeholder="Please enter your Remarks / Observations">
    <small id="nameHelp" class="form-text text-muted">Please enter your Remarks / Observations about these questions.</small>
  </div>
</div>
<br>
<a class="btn btn-primary btnPrevious">Previous</a>
<a class="btn btn-primary btnNextS1">Next</a>
</div> 


Get this bounty!!!

#StackBounty: #javascript #html #css #slider Make Same JS work for different Divisions Elements

Bounty: 100

I have two parts, First and Second both are sliders, while I slide for First, it happends for Second also, same for other actions. I do not want to use different JS for the same kind of actions, How can I, I tried a lot but failed.
both First and Second have same class.I have a bunch of divisions like this.

You can run and see what I mean.

$(".sliderWrapper1").prop("scrollWidth") > $(".catalog-cover").width() && $(".right-button").show();
const slider = document.querySelector(".sliderWrapper1"),
  preventClick = e => {
    e.preventDefault(), e.stopImmediatePropagation()
  };
let startX, scrollLeft, isDown = !1,
  isDragged = !1;
slider && (slider.addEventListener("mousedown", e => {
  isDown = !0, slider.classList.add("active"), startX = e.pageX - slider.offsetLeft, scrollLeft = slider.scrollLeft
}), slider.addEventListener("mouseleave", () => {
  isDown = !1, slider.classList.remove("active")
}), slider.addEventListener("mouseup", e => {
  isDown = !1;
  const t = document.querySelectorAll("a");
  if (isDragged)
    for (let e = 0; e < t.length; e++) t[e].addEventListener("click", preventClick);
  else
    for (let e = 0; e < t.length; e++) t[e].removeEventListener("click", preventClick);
  slider.classList.remove("active"), isDragged = !1
}), slider.addEventListener("mousemove", e => {
  if (!isDown) return;
  isDragged = !0, e.preventDefault();
  const t = 2 * (e.pageX - slider.offsetLeft - startX);
  slider.scrollLeft = scrollLeft - t
}));
var widthc = $(".catalog-cover").width() - 20;
$(".left-button").hide(), $(".left-button").click((function() {
  $(".right-button").show(), $(".sliderWrapper1").animate({
    scrollLeft: "-=" + widthc + "px"
  })
})), $(".right-button").click((function() {
  $(".left-button").show(), $(".sliderWrapper1").animate({
    scrollLeft: "+=" + widthc + "px"
  })
})), $((function() {
  $(".sliderWrapper1").scroll((function() {
    var e = $(".sliderWrapper1").outerWidth(),
      t = $(".sliderWrapper1")[0].scrollWidth,
      n = $(".sliderWrapper1").scrollLeft();
    matchright = parseInt(t - e);
    matchleft = parseInt(n);
    differencerl = matchright - matchleft;
    differencerl <= 5 ? $(".right-button").hide() : 0 === n ? $(".left-button").hide() : $(".right-button, .left-button").show()
  }))
}))
<link rel="stylesheet" href="https://wallpaperstacks.sgp1.digitaloceanspaces.com/mycss.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<div class="ibox bsh">
  <h4>First</h4>
  <div class="projects-catalog">
    <div class="catalog-cover">
      <i class="left-button"></i>
      <ul class="sliderWrapper1">
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Apple
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Banana
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Orange
          </div>
        </a>

        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Apple
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Banana
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Orange
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Apple
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Banana
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Orange
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Apple
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Banana
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Orange
          </div>
        </a>

        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Apple
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Banana
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Orange
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Apple
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Banana
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Orange
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Pine
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Grapes
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            Pomeogranate
          </div>
        </a>

      </ul>
      <i class="right-button"></i>
    </div>
  </div>
</div>





<div class="ibox bsh">
  <h4>Second</h4>
  <div class="projects-catalog">
    <div class="catalog-cover">
      <i class="left-button"></i>
      <ul class="sliderWrapper1">
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            January
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            February
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            March
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            April
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            May
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            June
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            June
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            June
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            June
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            June
          </div>
        </a>

        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            July
          </div>
        </a>

        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            August
          </div>
        </a>

        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            September
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            September
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            September
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            September
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            September
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            September
          </div>
        </a>
        <a class="slide" href="https://wikipedia.org">
          <img height="50" width="50" class="img-circle tags" src="https://images.unsplash.com/photo-1593642532400-2682810df593?ixid=MnwxMjA3fDF8MHxlZGl0b3JpYWwtZmVlZHwxfHx8ZW58MHx8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60">
          <div class="sidekro">
            September
          </div>
        </a>

      </ul>
      <i class="right-button"></i>
    </div>
  </div>
</div>


Get this bounty!!!

#StackBounty: #c# #html #asp.net-core #blazor Scroll click not working with NavigationManager.NavigateTo, and I can't use href beca…

Bounty: 100

I have a Blazor Server web app; .NET 5.

I am running into frustrations related to navigating between pages in my web app:

  • When I use NavigationManager.NavigateTo(uri, true), I am unable to open links using my scroll click (which would open the link in a new browser tab if I were to use href="uri" instead). It opens a new tab, but loads the same page I was already on.
  • When I use href="uri" instead, the scroll-click works. However, it introduces a new problem: with a ‘normal’ left-click, the new page loads but retains the scroll position of the previous page in a mobile browser (I have tested with mobile Safari, as well as the mobile emulator in Chrome).

I need to be able to scroll-click into a new tab, as well as have a new page load without retaining the previous page’s scroll position. Any tips?


Get this bounty!!!

#StackBounty: #javascript #html #jquery #css #graph Beeswarm plot not collapsing

Bounty: 250

I’m trying to make my own beeswarm plot, which is a plot of points across one dimension where the points move up as needed in order to not overlap with other points.

Mine has labels for each point, and I’m trying to work out the javascript to move them up from the baseline only if they collide with a previous point. However, they’re moving up regardless of if they collide or not.

Desired behavior: In the first example (depending on your screen width) ‚óŹ Juliet Francis should be down at the baseline instead of way up near the top, because it can exist at the baseline without colliding with any preceding points (ordered from right to left).

I’ve tested the collision function, and that seems to be working fine. The problem seems to be either in swarm() or collidesAll() functions.

I’ve been poking at this for a couple days and haven’t been able to get it working. A second pair of eyes would be appreciated.

$(function() {
  
  let $graphs = $('.graph')
  let $firstSetOfNames = $('.graph:first-child .graph-dd')
  
  const setup = () => {
    let longest = 0
    $firstSetOfNames.each(function() {
      longest = ($(this).width() > longest) ? $(this).width() : longest
    })
    $graphs.css('padding-right', longest + 'px')
  }
  
  const collides = ($e1, $e2) => {
    let e1x1 = $e1.offset().left
    let e1x2 = e1x1.x1 + $e1.outerWidth( true )
    let e2x1 = $e2.offset().left
    let e2x2 = e2x1.x1 + $e2.outerWidth( true )
    let x = ((e1x1 < e2x1) && (e2x1 < e1x2)) || ((e2x1 < e1x1) && (e1x1 < e2x2))
    let y = parseInt($e1.css('--y'), 10) === parseInt($e2.css('--y'), 10)
    return !!(x || y)
  }
  
  const collidesAll = ($people, $person, j) => {
    for (let i = 0; i < j; i++) {
      if (collides($person, $people.eq(i))) {
        return true
      }
    }
    return false
  }
  
  const swarm = () => {
    $graphs.each(function(i) {
      let $graph = $(this)
      let $people = $($graph.find('.graph-dd').get().reverse())
      $people.each(function(j) {
        let $person = $(this)
        let n = 1
        if (0 === j) {
          $person.css('--y', 1)
        } else {
          do {
            $person.css('--y', n++)
          } while (collidesAll($people, $person, j))
          $graph.css('--yMax', n)
        }
      })
    })
  }
  
  setup()
  swarm()
  
})
.graph {
  margin: 2rem;
  padding: calc(calc(var(--yMax, 0) * 1.1em) + 1rem) 1rem 1rem;
  border: 1px solid black;
  overflow: hidden;
}

.graph-dl {
  position: relative;
  display: flex;
  margin: 0;
  justify-content: space-between;
}
.graph-dl::before {
  content: "";
  position: absolute;
  display: block;
  height: 1px;
  top: calc(50% - 0.5px);
  left: 0;
  right: 0;
  background: gray;
  z-index: -1;
}

.graph-dt {
  background: black;
  width: 1px;
  height: 1em;
  border: 0.5em solid white;
  margin: 0 -0.5em;
}
.graph-dt span {
  display: none;
}

.graph-dd {
  position: absolute;
  display: block;
  top: 0.5em;
  margin: 0 0 0 calc(-0.5ex - 1px);
  left: calc(var(--percent) * 1%);
  white-space: nowrap;
  transform: translateY(calc(var(--y, 0) * -1.1em));
  transition: transform 0.3s;
}
.graph-dd::before {
  content: "";
  display: inline-block;
  background: blue;
  width: 1ex;
  height: 1ex;
  vertical-align: baseline;
  border-radius: 100%;
  margin-right: 0.2em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

<figure class="graph">
  <dl class="graph-dl">
    <dt class="graph-dt"><span>0</span></dt>
    <dd class="graph-dd" data-value="9" style="--percent: 9; --y: 1;">Zaccaria Osmant</dd>
    <dt class="graph-dt"><span>10</span></dt>
    <dd class="graph-dd" data-value="11" style="--percent: 11; --y: 1;">Nelli Dunge</dd>
    <dd class="graph-dd" data-value="12" style="--percent: 12; --y: 1;">Ethelind Evers</dd>
    <dd class="graph-dd" data-value="16" style="--percent: 16; --y: 1;">Juliet Francis</dd>
    <dt class="graph-dt"><span>20</span></dt>
    <dt class="graph-dt"><span>30</span></dt>
    <dt class="graph-dt"><span>40</span></dt>
    <dd class="graph-dd" data-value="48" style="--percent: 48; --y: 1;">Myles Burdoun</dd>
    <dt class="graph-dt"><span>50</span></dt>
    <dd class="graph-dd" data-value="55" style="--percent: 55; --y: 1;">Gregory Beade</dd>
    <dt class="graph-dt"><span>60</span></dt>
    <dd class="graph-dd" data-value="60" style="--percent: 60; --y: 1;">Trenna Vigne</dd>
    <dd class="graph-dd" data-value="61" style="--percent: 61; --y: 1;">Dulcia Koubu</dd>
    <dt class="graph-dt"><span>70</span></dt>
    <dd class="graph-dd" data-value="70" style="--percent: 70; --y: 1;">Amberly Wrightham</dd>
    <dd class="graph-dd" data-value="73" style="--percent: 73; --y: 1;">Barney Rawstorn</dd>
    <dt class="graph-dt"><span>80</span></dt>
    <dt class="graph-dt"><span>90</span></dt>
    <dd class="graph-dd" data-value="91" style="--percent: 91; --y: 1;">Nealson Helstrip</dd>
    <dd class="graph-dd" data-value="92" style="--percent: 92; --y: 1;">Asa Langwade</dd>
    <dd class="graph-dd" data-value="93" style="--percent: 93; --y: 1;">Malvin Imlaw</dd>
    <dd class="graph-dd" data-value="96" style="--percent: 96; --y: 1;">Joanie Clooney</dd>
    <dt class="graph-dt"><span>100</span></dt>
    <dd class="graph-dd" data-value="100" style="--percent: 100; --y: 1;">Kristo Biskupski</dd>
  </dl>
</figure>
<figure class="graph">
  <dl class="graph-dl">
    <dt class="graph-dt"><span>0</span></dt>
    <dt class="graph-dt"><span>10</span></dt>
    <dt class="graph-dt"><span>20</span></dt>
    <dt class="graph-dt"><span>30</span></dt>
    <dt class="graph-dt"><span>40</span></dt>
    <dd class="graph-dd" data-value="44" style="--percent: 44; --y: 1;">Nelli Dunge</dd>
    <dd class="graph-dd" data-value="48" style="--percent: 48; --y: 1;">Myles Burdoun</dd>
    <dt class="graph-dt"><span>50</span></dt>
    <dd class="graph-dd" data-value="51" style="--percent: 51; --y: 1;">Zaccaria Osmant</dd>
    <dt class="graph-dt"><span>60</span></dt>
    <dd class="graph-dd" data-value="61" style="--percent: 61; --y: 1;">Trenna Vigne</dd>
    <dd class="graph-dd" data-value="61" style="--percent: 61; --y: 1;">Dulcia Koubu</dd>
    <dd class="graph-dd" data-value="65" style="--percent: 65; --y: 1;">Ethelind Evers</dd>
    <dt class="graph-dt"><span>70</span></dt>
    <dd class="graph-dd" data-value="70" style="--percent: 70; --y: 1;">Amberly Wrightham</dd>
    <dd class="graph-dd" data-value="73" style="--percent: 73; --y: 1;">Barney Rawstorn</dd>
    <dd class="graph-dd" data-value="74" style="--percent: 74; --y: 1;">Kristo Biskupski</dd>
    <dd class="graph-dd" data-value="75" style="--percent: 75; --y: 1;">Joanie Clooney</dd>
    <dd class="graph-dd" data-value="77" style="--percent: 77; --y: 1;">Juliet Francis</dd>
    <dd class="graph-dd" data-value="77" style="--percent: 77; --y: 1;">Gregory Beade</dd>
    <dd class="graph-dd" data-value="79" style="--percent: 79; --y: 1;">Malvin Imlaw</dd>
    <dt class="graph-dt"><span>80</span></dt>
    <dd class="graph-dd" data-value="85" style="--percent: 85; --y: 1;">Asa Langwade</dd>
    <dt class="graph-dt"><span>90</span></dt>
    <dd class="graph-dd" data-value="91" style="--percent: 91; --y: 1;">Nealson Helstrip</dd>
    <dt class="graph-dt"><span>100</span></dt>
  </dl>
</figure>

Or view in CodePen with Pug and Sass instead.


Get this bounty!!!

#StackBounty: #html #css mix-blend-mode with table cell that have position sticky

Bounty: 200

I have very weird behavior that happens in both Google Chrome and Firefox.

I have a table with a gradient background that creates zebra stripes. The cells have a white background and I use mix blend mode to hide the cells that are sticky so only one sticky cell that is visible.

The problem is that when the table cell is sticky, blend mode doesn’t work properly and you see multiple cells at once. But if I put div inside the cell and make the div sticky it magically works. Any idea why?

The difference between the table is this:

.table-A .level-2 [rowspan] div,
.table-B .level-0 div,
.table-B td[rowspan] {
    position: sticky;
    top: 0;
}

And both tables have:

.level-2 [rowspan] {
    background: white;
    mix-blend-mode: multiply;
    vertical-align: top;
}

You can see the issue in this CodePen demo

and in this screen:

enter image description here

My question is why this is happening why cells work differently than div inside. As you can see from the demo I already know how to solve the issue I only want to know why it’s behaving like this.


Get this bounty!!!

#StackBounty: #javascript #html #css #ecmascript-6 #user-interface JavaScript discrete slider web component

Bounty: 50

The standard HTML range input element suffers from a number of limitations that caused me to implement the web component presented here.

First, it is unnecessarily complicated to style the standard HTML range input element to make it look the same in all web browsers (see here and here).

Second, the standard HTML range input element is designed to be used in a horizontal manner from left to right. Turning it into a vertical slider can be done but is again web browser-specific (see here). In order to invert its minimum and maximum the element needs to be transformed accordingly (see here).

Lastly, the standard HTML range input element does not support visual indications of the values that it can be set to. Such indications are an important aspect of this web component and it is where it derives its name from.

The result of that effort is a dependency-free web component stored in a single file. Its content is shown below:

export const OrientationEnum = Object.freeze({
  LeftToRight: "left-to-right",
  RightToLeft: "right-to-left",
  TopToBottom: "top-to-bottom",
  BottomToTop: "bottom-to-top"
});

const webComponentName      = "summbit-discrete-slider";
const nameTrack             = "track";
const nameMinimum           = "min";
const nameMaximum           = "max";
const nameValue             = "value";
const nameOrientation       = "orientation";
const nameThumb             = "thumb";
const nameDot               = "dot";
const nameDotContainer      = "dot-container";
const keysToIncrement       = ["ArrowUp", "ArrowRight"];
const keysToDecrement       = ["ArrowDown", "ArrowLeft"];
const propertyTrackColor    = `--${webComponentName}-private-track-color`;
const propertyProgressColor = `--${webComponentName}-private-progress-color`;
const propertyThumbDiameter = `--${webComponentName}-private-thumb-diameter`;
const propertyThumbPosition = `--${webComponentName}-private-thumb-position`;
const initialMinimum        = 0;
const initialMaximum        = 5;
const template              = document.createElement("template");
template.innerHTML          = `
<div id="${nameTrack}" class="${nameTrack}-${OrientationEnum.LeftToRight}" tabindex="0">
  <span id="${nameMinimum}" class="${nameMinimum}-${OrientationEnum.LeftToRight}" tabindex="-1"></span>
  <span id="${nameMaximum}" class="${nameMaximum}-${OrientationEnum.LeftToRight}" tabindex="-1"></span>
  <span id="${nameDotContainer}" class="${nameDotContainer}-${OrientationEnum.LeftToRight}" tabindex="-1"></span>
  <span id="${nameThumb}" class="${nameThumb}-${OrientationEnum.LeftToRight}" tabindex="-1">
    <span class="thumb-center-dot" tabindex="-1"></span>
  </span>
</div>
<style>
:host {
  ${propertyTrackColor}: var(--${webComponentName}-track-color, #a8bec9);
  ${propertyProgressColor}: var(--${webComponentName}-progress-color, #0079c9);
  ${propertyThumbDiameter}: var(--${webComponentName}-thumb-diameter, 20px);
  --${webComponentName}-private-thumb-color: var(--${webComponentName}-thumb-color, #ffffff);
  --${webComponentName}-private-track-width: var(--${webComponentName}-track-width, 4px);
  --${webComponentName}-private-track-offset: calc(calc(100% - var(--${webComponentName}-private-track-width)) / 2);
  --${webComponentName}-private-thumb-stroke: calc(var(--${webComponentName}-private-track-width) / 2);
  --${webComponentName}-private-thumb-radius: calc(var(${propertyThumbDiameter}) / 2);
  --${webComponentName}-private-thumb-offset: calc(calc(100% - var(${propertyThumbDiameter})) / 2);
  --${webComponentName}-private-dot-diameter: calc(var(--${webComponentName}-private-track-width) * 2);
  --${webComponentName}-private-dot-container-offset-front-side: calc(calc(var(${propertyThumbDiameter}) - var(--${webComponentName}-private-dot-diameter)) / 2);
  --${webComponentName}-private-dot-container-offset-long-side: calc(calc(100% - var(--${webComponentName}-private-dot-diameter)) / 2);
  all: initial;
  touch-action: none;
  display: inline-block;
  padding: 0;
  margin: 0;
}

.${nameTrack}-${OrientationEnum.LeftToRight},
.${nameTrack}-${OrientationEnum.RightToLeft},
.${nameTrack}-${OrientationEnum.TopToBottom},
.${nameTrack}-${OrientationEnum.BottomToTop} {
  touch-action: none;
  position: relative;
  width: 100%;
  height: 100%;
}

.${nameTrack}-${OrientationEnum.LeftToRight},
.${nameTrack}-${OrientationEnum.RightToLeft} {
  min-width: 150px;
  min-height: var(${propertyThumbDiameter});
}

.${nameTrack}-${OrientationEnum.TopToBottom},
.${nameTrack}-${OrientationEnum.BottomToTop} {
  min-height: 150px;
  min-width: var(${propertyThumbDiameter});
}

.${nameMinimum}-${OrientationEnum.LeftToRight},
.${nameMinimum}-${OrientationEnum.RightToLeft},
.${nameMinimum}-${OrientationEnum.TopToBottom},
.${nameMinimum}-${OrientationEnum.BottomToTop} {
  touch-action: none;
  position: absolute;
  background-color: var(${propertyProgressColor});
}

.${nameMaximum}-${OrientationEnum.LeftToRight},
.${nameMaximum}-${OrientationEnum.RightToLeft},
.${nameMaximum}-${OrientationEnum.TopToBottom},
.${nameMaximum}-${OrientationEnum.BottomToTop} {
  touch-action: none;
  position: absolute;
  background-color: var(${propertyTrackColor});
}

.${nameMinimum}-${OrientationEnum.LeftToRight},
.${nameMaximum}-${OrientationEnum.RightToLeft} {
  top: var(--${webComponentName}-private-track-offset);
  left: var(--${webComponentName}-private-thumb-radius);
  width: var(${propertyThumbPosition});
  height: var(--${webComponentName}-private-track-width);
}

.${nameMinimum}-${OrientationEnum.RightToLeft},
.${nameMaximum}-${OrientationEnum.LeftToRight} {
  top: var(--${webComponentName}-private-track-offset);
  left: calc(var(${propertyThumbPosition}) + var(--${webComponentName}-private-thumb-radius));
  right: var(--${webComponentName}-private-thumb-radius);
  height: var(--${webComponentName}-private-track-width);
}

.${nameMinimum}-${OrientationEnum.TopToBottom},
.${nameMaximum}-${OrientationEnum.BottomToTop} {
  top: var(--${webComponentName}-private-thumb-radius);
  left: var(--${webComponentName}-private-track-offset);
  width: var(--${webComponentName}-private-track-width);
  height: var(${propertyThumbPosition});
}

.${nameMinimum}-${OrientationEnum.BottomToTop},
.${nameMaximum}-${OrientationEnum.TopToBottom} {
  top: calc(var(${propertyThumbPosition}) + var(--${webComponentName}-private-thumb-radius));
  left: var(--${webComponentName}-private-track-offset);
  width: var(--${webComponentName}-private-track-width);
  bottom: var(--${webComponentName}-private-thumb-radius);
}

.${nameDotContainer}-${OrientationEnum.LeftToRight},
.${nameDotContainer}-${OrientationEnum.RightToLeft},
.${nameDotContainer}-${OrientationEnum.TopToBottom},
.${nameDotContainer}-${OrientationEnum.BottomToTop} {
  touch-action: none;
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.${nameDotContainer}-${OrientationEnum.LeftToRight},
.${nameDotContainer}-${OrientationEnum.RightToLeft} {
  top: var(--${webComponentName}-private-dot-container-offset-long-side);
  left: var(--${webComponentName}-private-dot-container-offset-front-side);
  right: var(--${webComponentName}-private-dot-container-offset-front-side);
  bottom: var(--${webComponentName}-private-dot-container-offset-long-side);
}

.${nameDotContainer}-${OrientationEnum.TopToBottom},
.${nameDotContainer}-${OrientationEnum.BottomToTop} {
  top: var(--${webComponentName}-private-dot-container-offset-front-side);
  left: var(--${webComponentName}-private-dot-container-offset-long-side);
  right: var(--${webComponentName}-private-dot-container-offset-long-side);
  bottom: var(--${webComponentName}-private-dot-container-offset-front-side);
}

.${nameDotContainer}-${OrientationEnum.LeftToRight} {
  flex-direction: row;
}

.${nameDotContainer}-${OrientationEnum.RightToLeft} {
  flex-direction: row-reverse;
}

.${nameDotContainer}-${OrientationEnum.TopToBottom} {
  flex-direction: column;
}

.${nameDotContainer}-${OrientationEnum.BottomToTop} {
  flex-direction: column-reverse;
}

.${nameDot} {
  touch-action: none;
  width: var(--${webComponentName}-private-dot-diameter);
  height: var(--${webComponentName}-private-dot-diameter);
  border-radius: 50%;
}

.${nameThumb}-${OrientationEnum.LeftToRight},
.${nameThumb}-${OrientationEnum.RightToLeft},
.${nameThumb}-${OrientationEnum.TopToBottom},
.${nameThumb}-${OrientationEnum.BottomToTop} {
  touch-action: none;
  position: absolute;
  width: var(${propertyThumbDiameter});
  height: var(${propertyThumbDiameter});
  border-radius: 50%;
  border: var(--${webComponentName}-private-thumb-stroke) solid var(${propertyProgressColor});
  box-sizing: border-box;
  background-color: var(--${webComponentName}-private-thumb-color);
  display: flex;
  align-items: center;
  justify-content: center;
}

.${nameThumb}-${OrientationEnum.LeftToRight},
.${nameThumb}-${OrientationEnum.RightToLeft} {
  top: var(--${webComponentName}-private-thumb-offset);
  left: var(${propertyThumbPosition});
}

.${nameThumb}-${OrientationEnum.TopToBottom},
.${nameThumb}-${OrientationEnum.BottomToTop} {
  top: var(${propertyThumbPosition});
  left: var(--${webComponentName}-private-thumb-offset);
}

.thumb-center-dot {
  touch-action: none;
  width: var(--${webComponentName}-private-dot-diameter);
  height: var(--${webComponentName}-private-dot-diameter);
  border-radius: 50%;
  background-color: var(${propertyProgressColor});
}

.${nameTrack}-${OrientationEnum.LeftToRight}:focus,
.${nameTrack}-${OrientationEnum.RightToLeft}:focus,
.${nameTrack}-${OrientationEnum.TopToBottom}:focus,
.${nameTrack}-${OrientationEnum.BottomToTop}:focus,
.${nameMinimum}-${OrientationEnum.LeftToRight}:focus,
.${nameMinimum}-${OrientationEnum.RightToLeft}:focus,
.${nameMinimum}-${OrientationEnum.TopToBottom}:focus,
.${nameMinimum}-${OrientationEnum.BottomToTop}:focus,
.${nameMaximum}-${OrientationEnum.LeftToRight}:focus,
.${nameMaximum}-${OrientationEnum.RightToLeft}:focus,
.${nameMaximum}-${OrientationEnum.TopToBottom}:focus,
.${nameMaximum}-${OrientationEnum.BottomToTop}:focus,
.${nameDotContainer}-${OrientationEnum.LeftToRight}:focus,
.${nameDotContainer}-${OrientationEnum.RightToLeft}:focus,
.${nameDotContainer}-${OrientationEnum.TopToBottom}:focus,
.${nameDotContainer}-${OrientationEnum.BottomToTop}:focus,
.${nameDot}:focus,
.${nameThumb}-${OrientationEnum.LeftToRight}:focus,
.${nameThumb}-${OrientationEnum.RightToLeft}:focus,
.${nameThumb}-${OrientationEnum.TopToBottom}:focus,
.${nameThumb}-${OrientationEnum.BottomToTop}:focus,
.thumb-center-dot:focus {
  outline: none;
}

.${nameTrack}-${OrientationEnum.LeftToRight}:focus-visible,
.${nameTrack}-${OrientationEnum.RightToLeft}:focus-visible,
.${nameTrack}-${OrientationEnum.TopToBottom}:focus-visible,
.${nameTrack}-${OrientationEnum.BottomToTop}:focus-visible {
  outline: 5px auto Highlight;
  outline: 5px auto -webkit-focus-ring-color;
}
</style>
`;

export default class SummbitDiscreteSlider extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    this.shadowRoot.appendChild(template.content.cloneNode(true));
    this._minimum             = Number.NaN;
    this._maximum             = Number.NaN;
    this._value               = 0;
    this._startValue          = 0;
    this._orientation         = OrientationEnum.LeftToRight;
    this._trackElement        = this.shadowRoot.getElementById(nameTrack);
    this._minimumElement      = this.shadowRoot.getElementById(nameMinimum);
    this._maximumElement      = this.shadowRoot.getElementById(nameMaximum);
    this._dotContainerElement = this.shadowRoot.getElementById(nameDotContainer);
    this._thumbElement        = this.shadowRoot.getElementById(nameThumb);
    this._resizeObserver      = new ResizeObserver(this._resizeHandler.bind(this));
    this.ondragstart          = () => false;
    this.addEventListener("pointerdown", this._pointerDownHandler.bind(this));
    this.addEventListener("keydown", this._keyDownHandler.bind(this));
  }

  static get observedAttributes() {
    return [nameMinimum, nameMaximum, nameValue, nameOrientation];
  }

  attributeChangedCallback(attributeName, oldValue, newValue) {
    if(newValue !== null) {
      switch(attributeName) {
        case nameMinimum:
          this._minimum = parseInt(newValue);
          this._createAllDots();
          this._updateThumbPosition();
          this._restrictValueToMinimum();
          break;
        case nameMaximum:
          this._maximum = parseInt(newValue);
          this._createAllDots();
          this._updateThumbPosition();
          this._restrictValueToMaximum();
          break;
        case nameValue:
          this._value = this._clampValue(parseInt(newValue));
          this._createAllDots();
          this._updateThumbPosition();
          this._updateDotColorsAndValues();
          break;
        case nameOrientation:
          this._orientation = newValue;
          this._updateElementStyle();
          this._createAllDots();
          this._updateThumbPosition();
          this._updateDotColorsAndValues();
          break;
      }
    }
  }

  connectedCallback() {
    this._createAllDots();
    this._resizeObserver.observe(this, { box: "content-box" });
  }

  set min(value) {
    this.setAttribute(nameMinimum, value);
  }

  get min() {
    return this._minimum;
  }

  set max(value) {
    this.setAttribute(nameMaximum, value);
  }

  get max() {
    return this._maximum;
  }

  set value(value) {
    this.setAttribute(nameValue, this._clampValue(value));
  }

  get value() {
    return this._value;
  }

  set orientation(value) {
    this.setAttribute(nameOrientation, value);
  }

  get orientation() {
    return this._orientation;
  }

  _resizeHandler(entries) {
    this._updateThumbPosition();
  }

  _pointerDownHandler(event) {
    this.setPointerCapture(event.pointerId);
    this.onpointermove = this._pointerMoveHandler.bind(this);
    this.onpointerup   = this._pointerUpHandler.bind(this);
    this._startValue   = this._value;
    this._updateValue(event);
  }

  _pointerMoveHandler(event) {
    this._updateValue(event);
  }

  _pointerUpHandler(event) {
    this.onpointermove = null;
    this.onpointerup   = null;
    if(this._startValue != this._value) {
      this._dispatchChangeEvent();
    }
  }

  _keyDownHandler(event) {
    if(keysToIncrement.includes(event.code)) {
      this._incrementValue();
    } else if(keysToDecrement.includes(event.code)) {
      this._decrementValue();
    }
  }

  _createAllDots() {
    this._initializeLimits();
    let currentDotCount  = this._dotContainerElement.childElementCount;
    let requiredDotCount = this._maximum - this._minimum + 1;
    this._createAdditionalDots(requiredDotCount - currentDotCount);
    this._deleteObsoleteDots(currentDotCount - requiredDotCount);
  }

  _initializeLimits() {
    if(isNaN(this._minimum)) {
      this._minimum = initialMinimum;
    }
    if(isNaN(this._maximum)) {
      this._maximum = initialMaximum;
    }
  }

  _createAdditionalDots(dotCount) {
    if(dotCount > 0) {
      for(let i = 0; i < dotCount; i++) {
        this._dotContainerElement.appendChild(this._createDot());
      }
      this._updateDotColorsAndValues();
    }
  }

  _deleteObsoleteDots(dotCount) {
    if(dotCount > 0) {
      for(let i = 0; i < dotCount; i++) {
        this._dotContainerElement.removeChild(this._dotContainerElement.lastChild);
      }
      this._updateDotColorsAndValues();
    }
  }

  _createDot() {
    let dot       = document.createElement("span");
    dot.className = nameDot;
    dot.tabIndex  = -1;
    return dot;
  }

  _updateDotColorsAndValues() {
    let computedStyle = window.getComputedStyle(this);
    let progressColor = computedStyle.getPropertyValue(propertyProgressColor);
    let trackColor    = computedStyle.getPropertyValue(propertyTrackColor);
    let dot, value;
    for(let i = 0; i < this._dotContainerElement.children.length; i++) {
      value                     = i + this._minimum;
      dot                       = this._dotContainerElement.children[i];
      dot.dataset.value         = value;
      dot.style.backgroundColor = value > this._value ? trackColor : progressColor;
    }
  }

  _updateThumbPosition() {
    for(let i = 0; i < this._dotContainerElement.children.length; i++) {
      if(this._dotContainerElement.children[i].dataset.value == this._value) {
        switch(this._orientation) {
          case OrientationEnum.LeftToRight:
          case OrientationEnum.RightToLeft:
            this._trackElement.style.setProperty(propertyThumbPosition, this._dotContainerElement.children[i].offsetLeft + "px", "");
            return;
          case OrientationEnum.TopToBottom:
          case OrientationEnum.BottomToTop:
            this._trackElement.style.setProperty(propertyThumbPosition, this._dotContainerElement.children[i].offsetTop + "px", "");
            return;
        }
      }
    }
  }

  _clampValue(value) {
    if(!isNaN(this._minimum) && value < this._minimum) {
      return this._minimum;
    } else if(!isNaN(this._maximum) && value > this._maximum) {
      return this._maximum;
    }
    return value;
  }

  _restrictValueToMinimum() {
    if(this._value < this._minimum) {
      this.value = this._minimum;
    }
  }

  _restrictValueToMaximum() {
    if(this._value > this._maximum) {
      this.value = this._maximum;
    }
  }

  _incrementValue() {
    if(this._value < this._maximum) {
      this.value += 1;
      this._dispatchInputEvent();
      this._dispatchChangeEvent();
    }
  }

  _decrementValue() {
    if(this._value > this._minimum) {
      this.value -= 1;
      this._dispatchInputEvent();
      this._dispatchChangeEvent();
    }
  }

  _updateValue(event) {
    let index = Math.round(this._calculateProgress(event) * (this._maximum - this._minimum));
    let value = parseInt(this._dotContainerElement.children[index].dataset.value);
    if(this._value != value) {
      this.value = value;
      this._dispatchInputEvent();
    }
  }

  _calculateProgress(event) {
    let thumbDiameter = parseFloat(window.getComputedStyle(this).getPropertyValue(propertyThumbDiameter));
    let thumbRadius   = thumbDiameter / 2;
    let trackLength, position;
    switch(this._orientation) {
      case OrientationEnum.LeftToRight:
        trackLength = this._trackElement.offsetWidth - thumbDiameter;
        position    = event.clientX - thumbRadius - this._trackElement.getBoundingClientRect().left;
        break;
      case OrientationEnum.RightToLeft:
        trackLength = this._trackElement.offsetWidth - thumbDiameter;
        position    = this._trackElement.getBoundingClientRect().right - event.clientX - thumbRadius;
        break;
      case OrientationEnum.TopToBottom:
        trackLength = this._trackElement.offsetHeight - thumbDiameter;
        position    = event.clientY - thumbRadius - this._trackElement.getBoundingClientRect().top;
        break;
      case OrientationEnum.BottomToTop:
        trackLength = this._trackElement.offsetHeight - thumbDiameter;
        position    = this._trackElement.getBoundingClientRect().bottom - event.clientY - thumbRadius;
        break;
    }
    return Math.min(Math.max(position, 0), trackLength) / trackLength;
  }

  _updateElementStyle() {
    this._trackElement       .className = `${nameTrack}-${this._orientation}`;
    this._minimumElement     .className = `${nameMinimum}-${this._orientation}`;
    this._maximumElement     .className = `${nameMaximum}-${this._orientation}`;
    this._dotContainerElement.className = `${nameDotContainer}-${this._orientation}`;
    this._thumbElement       .className = `${nameThumb}-${this._orientation}`;
  }

  _dispatchInputEvent() {
    this.shadowRoot.dispatchEvent(new CustomEvent("input", { bubbles: true, composed: true, detail: { value: this._value }}));
  }

  _dispatchChangeEvent() {
    this.shadowRoot.dispatchEvent(new CustomEvent("change", { bubbles: true, composed: true, detail: { value: this._value }}));
  }
}

if(!customElements.get(webComponentName)) {
  customElements.define(webComponentName, SummbitDiscreteSlider);
}

This code has been released to the public on GitHub under this license.

The instructions on how to use it are available here. An HTML test page is available here.

Please note that symbol names are long and descriptive on purpose. The goal of this file is to be as understandable for humans as possible. Those symbol names are later minified.

Note also that private symbol names start with an underscore (_) in order to mark them accordingly. At the time of writing this, private class fields are not available yet in Firefox according to caniuse.com. Once available, the underscores will be replaced with the # character.

I’m looking for hints/comments/advice in general i.e. HTML, CSS and JavaScript best practices, style tips, etc. are all welcome.


Known issues:

  • Safari on iOS 14 seems to have a bug in regards to the setPointerCapture API. Moving the thumb only works if the pointer is located within the slider. This possible bug is corroborated by this StackOverflow post.


Get this bounty!!!

#StackBounty: #javascript #html How to get the size after cropping of an uploaded image with javascript

Bounty: 50

I have the following input element: <input type="file" accept="image/*" multiple=""> inside of a div with the id ImageUploader.
The uploaded images get automatically cropped, but I have no influence on that functionality.

JS:

var upload = document.getElementById("ImageUploader");
upload.onchange=function(event){
    let img = new Image()
    //console.log(event);
    img.src = window.URL.createObjectURL(event.target.files[0]);
    
    img.onload = () => {
        
        console.log(event.target.files[0].initialCroppedAreaPixels);
        console.log(event.target.files[0]);
        };
};

The strange thing is that for some images it is able to log the cropped size to the console, but for other images event.target.files[0].initialCroppedAreaPixels just returns undefined, which I don’t understand, because in the next line, the file that gets logged into the console always has the initialCroppedAreaPixels attribute.
What’s the issue there?


Get this bounty!!!

#StackBounty: #javascript #html #css #ecmascript-6 JavaScript discrete slider web component

Bounty: 50

The standard HTML range input element suffers from a number of limitations that caused me to implement the web component presented here.

First, it is unnecessarily complicated to style the standard HTML range input element to make it look the same in all web browsers (see here and here).

Second, the standard HTML range input element is designed to be used in a horizontal manner from left to right. Turning it into a vertical slider can be done but is again web browser-specific (see here). In order to invert its minimum and maximum the element needs to be transformed accordingly (see here).

Lastly, the standard HTML range input element does not support visual indications of the values that it can be set to. Such indications are an important aspect of this web component and it is where it derives its name from.

The result of that effort is a dependency-free web component stored in a single file. Its content is shown below:

export const OrientationEnum = Object.freeze({
  LeftToRight: "left-to-right",
  RightToLeft: "right-to-left",
  TopToBottom: "top-to-bottom",
  BottomToTop: "bottom-to-top"
});

const webComponentName      = "summbit-discrete-slider";
const nameTrack             = "track";
const nameMinimum           = "min";
const nameMaximum           = "max";
const nameValue             = "value";
const nameOrientation       = "orientation";
const nameThumb             = "thumb";
const nameDot               = "dot";
const nameDotContainer      = "dot-container";
const keysToIncrement       = ["ArrowUp", "ArrowRight"];
const keysToDecrement       = ["ArrowDown", "ArrowLeft"];
const propertyTrackColor    = `--${webComponentName}-private-track-color`;
const propertyProgressColor = `--${webComponentName}-private-progress-color`;
const propertyThumbDiameter = `--${webComponentName}-private-thumb-diameter`;
const propertyThumbPosition = `--${webComponentName}-private-thumb-position`;
const initialMinimum        = 0;
const initialMaximum        = 5;
const template              = document.createElement("template");
template.innerHTML          = `
<div id="${nameTrack}" class="${nameTrack}-${OrientationEnum.LeftToRight}" tabindex="0">
  <span id="${nameMinimum}" class="${nameMinimum}-${OrientationEnum.LeftToRight}" tabindex="-1"></span>
  <span id="${nameMaximum}" class="${nameMaximum}-${OrientationEnum.LeftToRight}" tabindex="-1"></span>
  <span id="${nameDotContainer}" class="${nameDotContainer}-${OrientationEnum.LeftToRight}" tabindex="-1"></span>
  <span id="${nameThumb}" class="${nameThumb}-${OrientationEnum.LeftToRight}" tabindex="-1">
    <span class="thumb-center-dot" tabindex="-1"></span>
  </span>
</div>
<style>
:host {
  ${propertyTrackColor}: var(--${webComponentName}-track-color, #a8bec9);
  ${propertyProgressColor}: var(--${webComponentName}-progress-color, #0079c9);
  ${propertyThumbDiameter}: var(--${webComponentName}-thumb-diameter, 20px);
  --${webComponentName}-private-thumb-color: var(--${webComponentName}-thumb-color, #ffffff);
  --${webComponentName}-private-track-width: var(--${webComponentName}-track-width, 4px);
  --${webComponentName}-private-track-offset: calc(calc(100% - var(--${webComponentName}-private-track-width)) / 2);
  --${webComponentName}-private-thumb-stroke: calc(var(--${webComponentName}-private-track-width) / 2);
  --${webComponentName}-private-thumb-radius: calc(var(${propertyThumbDiameter}) / 2);
  --${webComponentName}-private-thumb-offset: calc(calc(100% - var(${propertyThumbDiameter})) / 2);
  --${webComponentName}-private-dot-diameter: calc(var(--${webComponentName}-private-track-width) * 2);
  --${webComponentName}-private-dot-container-offset-front-side: calc(calc(var(${propertyThumbDiameter}) - var(--${webComponentName}-private-dot-diameter)) / 2);
  --${webComponentName}-private-dot-container-offset-long-side: calc(calc(100% - var(--${webComponentName}-private-dot-diameter)) / 2);
  all: initial;
  touch-action: none;
  display: inline-block;
  padding: 0;
  margin: 0;
}

.${nameTrack}-${OrientationEnum.LeftToRight},
.${nameTrack}-${OrientationEnum.RightToLeft},
.${nameTrack}-${OrientationEnum.TopToBottom},
.${nameTrack}-${OrientationEnum.BottomToTop} {
  touch-action: none;
  position: relative;
  width: 100%;
  height: 100%;
}

.${nameTrack}-${OrientationEnum.LeftToRight},
.${nameTrack}-${OrientationEnum.RightToLeft} {
  min-width: 150px;
  min-height: var(${propertyThumbDiameter});
}

.${nameTrack}-${OrientationEnum.TopToBottom},
.${nameTrack}-${OrientationEnum.BottomToTop} {
  min-height: 150px;
  min-width: var(${propertyThumbDiameter});
}

.${nameMinimum}-${OrientationEnum.LeftToRight},
.${nameMinimum}-${OrientationEnum.RightToLeft},
.${nameMinimum}-${OrientationEnum.TopToBottom},
.${nameMinimum}-${OrientationEnum.BottomToTop} {
  touch-action: none;
  position: absolute;
  background-color: var(${propertyProgressColor});
}

.${nameMaximum}-${OrientationEnum.LeftToRight},
.${nameMaximum}-${OrientationEnum.RightToLeft},
.${nameMaximum}-${OrientationEnum.TopToBottom},
.${nameMaximum}-${OrientationEnum.BottomToTop} {
  touch-action: none;
  position: absolute;
  background-color: var(${propertyTrackColor});
}

.${nameMinimum}-${OrientationEnum.LeftToRight},
.${nameMaximum}-${OrientationEnum.RightToLeft} {
  top: var(--${webComponentName}-private-track-offset);
  left: var(--${webComponentName}-private-thumb-radius);
  width: var(${propertyThumbPosition});
  height: var(--${webComponentName}-private-track-width);
}

.${nameMinimum}-${OrientationEnum.RightToLeft},
.${nameMaximum}-${OrientationEnum.LeftToRight} {
  top: var(--${webComponentName}-private-track-offset);
  left: calc(var(${propertyThumbPosition}) + var(--${webComponentName}-private-thumb-radius));
  right: var(--${webComponentName}-private-thumb-radius);
  height: var(--${webComponentName}-private-track-width);
}

.${nameMinimum}-${OrientationEnum.TopToBottom},
.${nameMaximum}-${OrientationEnum.BottomToTop} {
  top: var(--${webComponentName}-private-thumb-radius);
  left: var(--${webComponentName}-private-track-offset);
  width: var(--${webComponentName}-private-track-width);
  height: var(${propertyThumbPosition});
}

.${nameMinimum}-${OrientationEnum.BottomToTop},
.${nameMaximum}-${OrientationEnum.TopToBottom} {
  top: calc(var(${propertyThumbPosition}) + var(--${webComponentName}-private-thumb-radius));
  left: var(--${webComponentName}-private-track-offset);
  width: var(--${webComponentName}-private-track-width);
  bottom: var(--${webComponentName}-private-thumb-radius);
}

.${nameDotContainer}-${OrientationEnum.LeftToRight},
.${nameDotContainer}-${OrientationEnum.RightToLeft},
.${nameDotContainer}-${OrientationEnum.TopToBottom},
.${nameDotContainer}-${OrientationEnum.BottomToTop} {
  touch-action: none;
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.${nameDotContainer}-${OrientationEnum.LeftToRight},
.${nameDotContainer}-${OrientationEnum.RightToLeft} {
  top: var(--${webComponentName}-private-dot-container-offset-long-side);
  left: var(--${webComponentName}-private-dot-container-offset-front-side);
  right: var(--${webComponentName}-private-dot-container-offset-front-side);
  bottom: var(--${webComponentName}-private-dot-container-offset-long-side);
}

.${nameDotContainer}-${OrientationEnum.TopToBottom},
.${nameDotContainer}-${OrientationEnum.BottomToTop} {
  top: var(--${webComponentName}-private-dot-container-offset-front-side);
  left: var(--${webComponentName}-private-dot-container-offset-long-side);
  right: var(--${webComponentName}-private-dot-container-offset-long-side);
  bottom: var(--${webComponentName}-private-dot-container-offset-front-side);
}

.${nameDotContainer}-${OrientationEnum.LeftToRight} {
  flex-direction: row;
}

.${nameDotContainer}-${OrientationEnum.RightToLeft} {
  flex-direction: row-reverse;
}

.${nameDotContainer}-${OrientationEnum.TopToBottom} {
  flex-direction: column;
}

.${nameDotContainer}-${OrientationEnum.BottomToTop} {
  flex-direction: column-reverse;
}

.${nameDot} {
  touch-action: none;
  width: var(--${webComponentName}-private-dot-diameter);
  height: var(--${webComponentName}-private-dot-diameter);
  border-radius: 50%;
}

.${nameThumb}-${OrientationEnum.LeftToRight},
.${nameThumb}-${OrientationEnum.RightToLeft},
.${nameThumb}-${OrientationEnum.TopToBottom},
.${nameThumb}-${OrientationEnum.BottomToTop} {
  touch-action: none;
  position: absolute;
  width: var(${propertyThumbDiameter});
  height: var(${propertyThumbDiameter});
  border-radius: 50%;
  border: var(--${webComponentName}-private-thumb-stroke) solid var(${propertyProgressColor});
  box-sizing: border-box;
  background-color: var(--${webComponentName}-private-thumb-color);
  display: flex;
  align-items: center;
  justify-content: center;
}

.${nameThumb}-${OrientationEnum.LeftToRight},
.${nameThumb}-${OrientationEnum.RightToLeft} {
  top: var(--${webComponentName}-private-thumb-offset);
  left: var(${propertyThumbPosition});
}

.${nameThumb}-${OrientationEnum.TopToBottom},
.${nameThumb}-${OrientationEnum.BottomToTop} {
  top: var(${propertyThumbPosition});
  left: var(--${webComponentName}-private-thumb-offset);
}

.thumb-center-dot {
  touch-action: none;
  width: var(--${webComponentName}-private-dot-diameter);
  height: var(--${webComponentName}-private-dot-diameter);
  border-radius: 50%;
  background-color: var(${propertyProgressColor});
}

.${nameTrack}-${OrientationEnum.LeftToRight}:focus,
.${nameTrack}-${OrientationEnum.RightToLeft}:focus,
.${nameTrack}-${OrientationEnum.TopToBottom}:focus,
.${nameTrack}-${OrientationEnum.BottomToTop}:focus,
.${nameMinimum}-${OrientationEnum.LeftToRight}:focus,
.${nameMinimum}-${OrientationEnum.RightToLeft}:focus,
.${nameMinimum}-${OrientationEnum.TopToBottom}:focus,
.${nameMinimum}-${OrientationEnum.BottomToTop}:focus,
.${nameMaximum}-${OrientationEnum.LeftToRight}:focus,
.${nameMaximum}-${OrientationEnum.RightToLeft}:focus,
.${nameMaximum}-${OrientationEnum.TopToBottom}:focus,
.${nameMaximum}-${OrientationEnum.BottomToTop}:focus,
.${nameDotContainer}-${OrientationEnum.LeftToRight}:focus,
.${nameDotContainer}-${OrientationEnum.RightToLeft}:focus,
.${nameDotContainer}-${OrientationEnum.TopToBottom}:focus,
.${nameDotContainer}-${OrientationEnum.BottomToTop}:focus,
.${nameDot}:focus,
.${nameThumb}-${OrientationEnum.LeftToRight}:focus,
.${nameThumb}-${OrientationEnum.RightToLeft}:focus,
.${nameThumb}-${OrientationEnum.TopToBottom}:focus,
.${nameThumb}-${OrientationEnum.BottomToTop}:focus,
.thumb-center-dot:focus {
  outline: none;
}

.${nameTrack}-${OrientationEnum.LeftToRight}:focus-visible,
.${nameTrack}-${OrientationEnum.RightToLeft}:focus-visible,
.${nameTrack}-${OrientationEnum.TopToBottom}:focus-visible,
.${nameTrack}-${OrientationEnum.BottomToTop}:focus-visible {
  outline: 5px auto Highlight;
  outline: 5px auto -webkit-focus-ring-color;
}
</style>
`;

export default class SummbitDiscreteSlider extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    this.shadowRoot.appendChild(template.content.cloneNode(true));
    this._minimum             = Number.NaN;
    this._maximum             = Number.NaN;
    this._value               = 0;
    this._startValue          = 0;
    this._orientation         = OrientationEnum.LeftToRight;
    this._trackElement        = this.shadowRoot.getElementById(nameTrack);
    this._minimumElement      = this.shadowRoot.getElementById(nameMinimum);
    this._maximumElement      = this.shadowRoot.getElementById(nameMaximum);
    this._dotContainerElement = this.shadowRoot.getElementById(nameDotContainer);
    this._thumbElement        = this.shadowRoot.getElementById(nameThumb);
    this._resizeObserver      = new ResizeObserver(this._resizeHandler.bind(this));
    this.ondragstart          = () => false;
    this.addEventListener("pointerdown", this._pointerDownHandler.bind(this));
    this.addEventListener("keydown", this._keyDownHandler.bind(this));
  }

  static get observedAttributes() {
    return [nameMinimum, nameMaximum, nameValue, nameOrientation];
  }

  attributeChangedCallback(attributeName, oldValue, newValue) {
    if(newValue !== null) {
      switch(attributeName) {
        case nameMinimum:
          this._minimum = parseInt(newValue);
          this._createAllDots();
          this._updateThumbPosition();
          this._restrictValueToMinimum();
          break;
        case nameMaximum:
          this._maximum = parseInt(newValue);
          this._createAllDots();
          this._updateThumbPosition();
          this._restrictValueToMaximum();
          break;
        case nameValue:
          this._value = this._clampValue(parseInt(newValue));
          this._createAllDots();
          this._updateThumbPosition();
          this._updateDotColorsAndValues();
          break;
        case nameOrientation:
          this._orientation = newValue;
          this._updateElementStyle();
          this._createAllDots();
          this._updateThumbPosition();
          this._updateDotColorsAndValues();
          break;
      }
    }
  }

  connectedCallback() {
    this._createAllDots();
    this._resizeObserver.observe(this, { box: "content-box" });
  }

  set min(value) {
    this.setAttribute(nameMinimum, value);
  }

  get min() {
    return this._minimum;
  }

  set max(value) {
    this.setAttribute(nameMaximum, value);
  }

  get max() {
    return this._maximum;
  }

  set value(value) {
    this.setAttribute(nameValue, this._clampValue(value));
  }

  get value() {
    return this._value;
  }

  set orientation(value) {
    this.setAttribute(nameOrientation, value);
  }

  get orientation() {
    return this._orientation;
  }

  _resizeHandler(entries) {
    this._updateThumbPosition();
  }

  _pointerDownHandler(event) {
    this.setPointerCapture(event.pointerId);
    this.onpointermove = this._pointerMoveHandler.bind(this);
    this.onpointerup   = this._pointerUpHandler.bind(this);
    this._startValue   = this._value;
    this._updateValue(event);
  }

  _pointerMoveHandler(event) {
    this._updateValue(event);
  }

  _pointerUpHandler(event) {
    this.onpointermove = null;
    this.onpointerup   = null;
    if(this._startValue != this._value) {
      this._dispatchChangeEvent();
    }
  }

  _keyDownHandler(event) {
    if(keysToIncrement.includes(event.code)) {
      this._incrementValue();
    } else if(keysToDecrement.includes(event.code)) {
      this._decrementValue();
    }
  }

  _createAllDots() {
    this._initializeLimits();
    let currentDotCount  = this._dotContainerElement.childElementCount;
    let requiredDotCount = this._maximum - this._minimum + 1;
    this._createAdditionalDots(requiredDotCount - currentDotCount);
    this._deleteObsoleteDots(currentDotCount - requiredDotCount);
  }

  _initializeLimits() {
    if(isNaN(this._minimum)) {
      this._minimum = initialMinimum;
    }
    if(isNaN(this._maximum)) {
      this._maximum = initialMaximum;
    }
  }

  _createAdditionalDots(dotCount) {
    if(dotCount > 0) {
      for(let i = 0; i < dotCount; i++) {
        this._dotContainerElement.appendChild(this._createDot());
      }
      this._updateDotColorsAndValues();
    }
  }

  _deleteObsoleteDots(dotCount) {
    if(dotCount > 0) {
      for(let i = 0; i < dotCount; i++) {
        this._dotContainerElement.removeChild(this._dotContainerElement.lastChild);
      }
      this._updateDotColorsAndValues();
    }
  }

  _createDot() {
    let dot       = document.createElement("span");
    dot.className = nameDot;
    dot.tabIndex  = -1;
    return dot;
  }

  _updateDotColorsAndValues() {
    let computedStyle = window.getComputedStyle(this);
    let progressColor = computedStyle.getPropertyValue(propertyProgressColor);
    let trackColor    = computedStyle.getPropertyValue(propertyTrackColor);
    let dot, value;
    for(let i = 0; i < this._dotContainerElement.children.length; i++) {
      value                     = i + this._minimum;
      dot                       = this._dotContainerElement.children[i];
      dot.dataset.value         = value;
      dot.style.backgroundColor = value > this._value ? trackColor : progressColor;
    }
  }

  _updateThumbPosition() {
    for(let i = 0; i < this._dotContainerElement.children.length; i++) {
      if(this._dotContainerElement.children[i].dataset.value == this._value) {
        switch(this._orientation) {
          case OrientationEnum.LeftToRight:
          case OrientationEnum.RightToLeft:
            this._trackElement.style.setProperty(propertyThumbPosition, this._dotContainerElement.children[i].offsetLeft + "px", "");
            return;
          case OrientationEnum.TopToBottom:
          case OrientationEnum.BottomToTop:
            this._trackElement.style.setProperty(propertyThumbPosition, this._dotContainerElement.children[i].offsetTop + "px", "");
            return;
        }
      }
    }
  }

  _clampValue(value) {
    if(!isNaN(this._minimum) && value < this._minimum) {
      return this._minimum;
    } else if(!isNaN(this._maximum) && value > this._maximum) {
      return this._maximum;
    }
    return value;
  }

  _restrictValueToMinimum() {
    if(this._value < this._minimum) {
      this.value = this._minimum;
    }
  }

  _restrictValueToMaximum() {
    if(this._value > this._maximum) {
      this.value = this._maximum;
    }
  }

  _incrementValue() {
    if(this._value < this._maximum) {
      this.value += 1;
      this._dispatchInputEvent();
      this._dispatchChangeEvent();
    }
  }

  _decrementValue() {
    if(this._value > this._minimum) {
      this.value -= 1;
      this._dispatchInputEvent();
      this._dispatchChangeEvent();
    }
  }

  _updateValue(event) {
    let index = Math.round(this._calculateProgress(event) * (this._maximum - this._minimum));
    let value = parseInt(this._dotContainerElement.children[index].dataset.value);
    if(this._value != value) {
      this.value = value;
      this._dispatchInputEvent();
    }
  }

  _calculateProgress(event) {
    let thumbDiameter = parseFloat(window.getComputedStyle(this).getPropertyValue(propertyThumbDiameter));
    let thumbRadius   = thumbDiameter / 2;
    let trackLength, position;
    switch(this._orientation) {
      case OrientationEnum.LeftToRight:
        trackLength = this._trackElement.offsetWidth - thumbDiameter;
        position    = event.clientX - thumbRadius - this._trackElement.getBoundingClientRect().left;
        break;
      case OrientationEnum.RightToLeft:
        trackLength = this._trackElement.offsetWidth - thumbDiameter;
        position    = this._trackElement.getBoundingClientRect().right - event.clientX - thumbRadius;
        break;
      case OrientationEnum.TopToBottom:
        trackLength = this._trackElement.offsetHeight - thumbDiameter;
        position    = event.clientY - thumbRadius - this._trackElement.getBoundingClientRect().top;
        break;
      case OrientationEnum.BottomToTop:
        trackLength = this._trackElement.offsetHeight - thumbDiameter;
        position    = this._trackElement.getBoundingClientRect().bottom - event.clientY - thumbRadius;
        break;
    }
    return Math.min(Math.max(position, 0), trackLength) / trackLength;
  }

  _updateElementStyle() {
    this._trackElement       .className = `${nameTrack}-${this._orientation}`;
    this._minimumElement     .className = `${nameMinimum}-${this._orientation}`;
    this._maximumElement     .className = `${nameMaximum}-${this._orientation}`;
    this._dotContainerElement.className = `${nameDotContainer}-${this._orientation}`;
    this._thumbElement       .className = `${nameThumb}-${this._orientation}`;
  }

  _dispatchInputEvent() {
    this.shadowRoot.dispatchEvent(new CustomEvent("input", { bubbles: true, composed: true, detail: { value: this._value }}));
  }

  _dispatchChangeEvent() {
    this.shadowRoot.dispatchEvent(new CustomEvent("change", { bubbles: true, composed: true, detail: { value: this._value }}));
  }
}

if(!customElements.get(webComponentName)) {
  customElements.define(webComponentName, SummbitDiscreteSlider);
}

This code has been released to the public on GitHub under this license.

The instructions on how to use it are available here. An HTML test page is available here.

Please note that symbol names are long and descriptive on purpose. The goal of this file is to be as understandable for humans as possible. Those symbol names are later minified.

Note also that private symbol names start with an underscore (_) in order to mark them accordingly. At the time of writing this, private class fields are not available yet in Firefox according to caniuse.com. Once available, the underscores will be replaced with the # character.

I’m looking for hints/comments/advice in general i.e. HTML, CSS and JavaScript best practices, style tips, etc. are all welcome.


Known issues:

  • Safari on iOS 14 seems to have a bug in regards to the setPointerCapture API. Moving the thumb only works if the pointer is located within the slider. This possible bug is corroborated by this StackOverflow post.


Get this bounty!!!