Conteúdo deste artigo
Um seletor de intervalo de datas permite que os usuários escolham um período entre uma data de início e uma data de término, o que é útil para reservar viagens, classificar informações por blocos de datas, escolher intervalos de tempo e planejar programações.

Vou mostrar um exemplo onde, mesmo que JavaScript esteja envolvido, a maior parte do trabalho é feita pela sintaxe “n de seletor(es)” do seletor CSS :nth-child, facilitando a construção da seleção de intervalo.
A sintaxe “n do seletor”
Esta sintaxe do :nth-child seletor filtra os elementos por um determinado seletor primeiro entre todos os elementos filhos, antes de selecioná-los por uma ordem de contagem .
<p>The reclamation of land...</p>
<p>The first reclamations can be traced...</p>
<p class="accent">By 1996, a total of...</p>
<p>Much reclamation has taken...</p>
<p class="accent">Hong Kong legislators...</p>
.accent {
color: red;
}
.accent:nth-child(2) {
font-weight: bold; /* does not work */
}
:nth-child(2 of .accent){
text-decoration: underline;
}
Existem dois parágrafos <p>The reclamation of land...</p>-ed com
<p>The first reclamations can be traced...</p>
<p class="accent">By 1996, a total of...</p>
<p>Much reclamation has taken...</p>
<p class="accent">Hong Kong legislators...</p>.accent { texto. À medida que tentamos direcionar o segundo parágrafo acentuado,
color: red;
}
.accent:nth-child(2) {
font-weight: bold; /* does not work */
}
:nth-child(2 of .accent){
text-decoration: underline;
}.accent falha ao selecioná-lo porque está tentando encontrar um <p>The reclamation of land...</p> elemento que é o segundo filho de seu pai .
<p>The first reclamations can be traced...</p>
<p class="accent">By 1996, a total of...</p>
<p>Much reclamation has taken...</p>
<p class="accent">Hong Kong legislators...</p>
Considerando que, .accent:nth-child(2) consegue selecionar e estilizar o segundo parágrafo acentuado porque está procurando apenas o segundo elemento entre o .accent elementos em vez do segundo de todos os filhos.
O layout
Passando para nosso exemplo principal, vamos montar um layout de mês. São necessárias apenas algumas linhas de CSS.
<ul id="calendar">
<li class="day">Mon</li>
<li class="day">Tue</li>
<!-- up to Sat -->
<li class="date">01<input type="checkbox" value="01"></li>
<li class="date">02<input type="checkbox" value="02"></li>
<!-- up to 31 -->
</ul>
#calendar {
display: grid;
grid-template-columns: repeat(7, 1fr); /* 7 for no. of days in a week */
}
Escolha apenas duas datas
Agora é quando recorremos ao JavaScript, já que não podemos marcar/desmarcar um controle em CSS. Mas mesmo aqui a sintaxe “n do seletor” pode ser muito útil.
Quando escolhemos duas datas para criar um intervalo, clicar em uma terceira data atualizará o intervalo e removerá uma das datas anteriores.
Você pode configurar a lógica de reajuste de faixa da maneira que desejar. Estou usando esta abordagem: se a terceira data for anterior ou posterior à último retorno data, ela se tornará a nova data de retorno , e a antiga será desmarcada. Se a terceira data for anterior à última data posterior, ela se tornará a nova data posterior e a antiga será desmarcada.
const CAL = document.getElementById('calendar');
const DT = Array.from(CAL.getElementsByClassName('date'));
CAL.addEventListener('change', e => {
if (!CAL.querySelector(':checked')) return;
/* When there are two checked boxes, calendar gets 'isRangeSelected' class */
CAL.className = CAL.querySelector(':nth-child(2 of :has(:checked))') ? 'isRangeSelected':'';
/* When there are three checked boxes */
if (CAL.querySelector(':nth-child(3 of :has(:checked))')) {
switch (DT.indexOf(e.target.parentElement)) {
/* If the newly checked date is first among the checked ones,
the second checked is unchecked. Onward date moved earlier. */
case DT.indexOf(CAL.querySelector(':nth-child(1 of :has(:checked))')):
CAL.querySelector(':nth-child(2 of :has(:checked)) input').checked = 0;
break;
/* If the newly checked date is second among the checked ones,
the third checked is unchecked. Return date moved earlier. */
case DT.indexOf(CAL.querySelector(':nth-child(2 of :has(:checked))')):
CAL.querySelector(':nth-child(3 of :has(:checked)) input').checked = 0;
break;
/* If the newly checked date is third among the checked ones,
the second checked is unchecked. Return date moved later. */
case DT.indexOf(CAL.querySelector(':nth-child(3 of :has(:checked))')):
CAL.querySelector(':nth-child(2 of :has(:checked)) input').checked = 0;
break;
}
}
});
Primeiro, obtemos o índice da data atual verificada (:nth-child(2 of .accent)), depois vemos se é igual ao primeiro verificado entre todos os verificados (**.accent**), segundo (<ul id="calendar">) ou terceiro (
<li class="day">Mon</li>
<li class="day">Tue</li>
<!-- up to Sat -->
<li class="date">01<input type="checkbox" value="01"></li>
<li class="date">02<input type="checkbox" value="02"></li>
<!-- up to 31 -->
</ul>#calendar {). Diante disso, desmarcamos a caixa relevante para revisar o intervalo de datas.
display: grid;
grid-template-columns: repeat(7, 1fr); /* 7 for no. of days in a week */
}
Você notará que usando a sintaxe “n do seletor”, visando a caixa const CAL = document.getElementById('calendar');
const DT = Array.from(CAL.getElementsByClassName('date'));
CAL.addEventListener('change', e => {
if (!CAL.querySelector(':checked')) return;
/* When there are two checked boxes, calendar gets 'isRangeSelected' class */
CAL.className = CAL.querySelector(':nth-child(2 of :has(:checked))') ? 'isRangeSelected':'';
/* When there are three checked boxes */
if (CAL.querySelector(':nth-child(3 of :has(:checked))')) {
switch (DT.indexOf(e.target.parentElement)) {
/* If the newly checked date is first among the checked ones,
the second checked is unchecked. Onward date moved earlier. */
case DT.indexOf(CAL.querySelector(':nth-child(1 of :has(:checked))')):
CAL.querySelector(':nth-child(2 of :has(:checked)) input').checked = 0;
break;
/* If the newly checked date is second among the checked ones,
the third checked is unchecked. Return date moved earlier. */
case DT.indexOf(CAL.querySelector(':nth-child(2 of :has(:checked))')):
CAL.querySelector(':nth-child(3 of :has(:checked)) input').checked = 0;
break;
/* If the newly checked date is third among the checked ones,
the second checked is unchecked. Return date moved later. */
case DT.indexOf(CAL.querySelector(':nth-child(3 of :has(:checked))')):
CAL.querySelector(':nth-child(2 of :has(:checked)) input').checked = 0;
break;
}
}
});
que queremos por sua posição entre todas as marcadas é muito mais simples – em vez de indexar por meio de uma lista de marcadas datas em JavaScript para isso, podemos selecioná-lo diretamente.
Estilizar a linha é ainda mais fácil do que isso.
Estilizando o intervalo
/* When two dates are selected */
.isRangeSelected {
/* Dates following the first but not the second of selected */
:nth-child(1 of :has(:checked)) ~ :not(:nth-child(2 of :has(:checked)) ~ .date) {
/* Range color */
background-color: rgb(228 239 253);
}
}
Quando há duas datas escolhidas, as datas entre a primeira (DT.indexOf(e.target.parentElement)) e a segunda (:nth-child(1 of :has(:checked))) são coloridas em azul claro, criando um alcance visual para esse bloco de datas no mês.

A cor é declarada dentro de um seletor composto que seleciona datas (:nth-child(2 of :has(:checked))) após a primeira de todas as datas verificadas (**.accent**), mas não a segunda de todas as datas verificadas (:checked).
Aqui está o exemplo completo mais uma vez:
Selecionando um intervalo de datas em CSS publicado originalmente em CSS-Tricks , que faz parte da família DigitalOcean .
