Created
June 8, 2022 19:23
-
-
Save nfreear/d61ec03df08618a2c8abc40a301c770d to your computer and use it in GitHub Desktop.
Date-picker / Calendar custom element/ Web component
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> <title> Date picker calendar </title> | |
<style> | |
body { font: 1rem/1.7 sans-serif; margin: 1rem auto; max-width: 36rem; } | |
table { border: 1px solid #ddd; line-height: 2; X-width: 99%; } | |
th, td { border: 1px solid #ddd; min-width: 3.5rem; text-align: center; } | |
th { background: #eee; color: #444; } | |
button, summary { cursor: pointer; font: inherit; padding: .2rem .5rem; } | |
summary { border: 1px solid #ccc; display: inline-block; } | |
.details-inner { | |
border: 1px solid #ccc; | |
padding: .5rem; | |
position: absolute; | |
} | |
my-date-picker * { | |
border-radius: .2rem; | |
outline-offset: .3rem; | |
transition: all 500ms; | |
} | |
[aria-current], [aria-selected] { | |
cursor: help; | |
} | |
[ aria-current ] { | |
background: #eee; | |
color: #a00; | |
font-weight: bold; | |
} | |
[ aria-current ]:after { | |
content: ' [c]'; | |
font-size: x-small; | |
} | |
[ aria-selected = true ] { | |
background: #ddd; | |
color: #080; | |
font-weight: bold; | |
} | |
[ aria-selected = true ]:after { | |
content: ' [s]'; | |
font-size: x-small; | |
} | |
[ role = gridcell ][ tabindex ] { | |
cursor: pointer; | |
} | |
[ role = gridcell ][ tabindex ]:hover, | |
[ role = gridcell ][ tabindex ]:focus { | |
background: #eee; | |
border-color: black; | |
} | |
</style> | |
<h1> Date picker calendar </h1> | |
<my-date-picker selected="2022-06-09" today="2022-06-08"> | |
<details> | |
<summary>📅 calendar</summary> | |
<div class="details-inner"> | |
<button class="prev">← Previous</button> | |
<button class="next">Next →</button> | |
<table> | |
<caption> <time datetime="2022-06">June 2022</time> </caption> | |
<thead> | |
<tr> | |
<th>Sun</th> <th>Mon</th> <th>Tue</th> <th>Wed</th> <th>Thu</th> <th>Fri</th> <th>Sat</th> | |
</tr> | |
</thead> | |
<tbody role="grid"> | |
<tr> | |
<td>29</td> <td>30</td> <td>31</td> <td>1</td> <td>2</td> <td>3</td> <td>4</td> | |
</tr> | |
<tr> | |
<td>5</td> <td>6</td> <td>7</td> <td>8</td> <td>9</td> <td>10</td> <td>11</td> | |
</tr> | |
<tr> | |
<td>12</td> <td>13</td> <td>14</td> <td>15</td> <td>16</td> <td>17</td> <td>18</td> | |
</tr> | |
<tr> | |
<td>19</td> <td>20</td> <td>21</td> <td>22</td> <td>23</td> <td>24</td> <td>25</td> | |
</tr> | |
<tr> | |
<td>26</td> <td>27</td> <td>28</td> <td>29</td> <td>30</td> <td>1</td> <td>2</td> | |
</tr> | |
</tbody> | |
</table> | |
</div> | |
</details> | |
</my-date-picker> | |
<script type="module"> | |
const PICKER = document.querySelector('my-date-picker'); | |
const MONTH = PICKER.querySelector('time').getAttribute('datetime'); | |
const GRID = PICKER.querySelector('table tbody'); | |
const ROWS = GRID.querySelectorAll('tr'); | |
const CELLS = GRID.querySelectorAll('td'); | |
const DT_TODAY = new Date(PICKER.getAttribute('today')); | |
const DT_SELECTED = new Date(PICKER.getAttribute('selected')); | |
ROWS.forEach((row, week) => { | |
row.setAttribute('data-week', week); | |
const cells = row.querySelectorAll('td'); | |
cells.forEach((cell, idx) => { | |
cell.setAttribute('role', 'gridcell'); | |
cell.setAttribute('tabindex', -1); | |
cell.setAttribute('data-col', idx); | |
cell.setAttribute('data-coord', `${idx},${week}`); | |
}); | |
}); | |
function reset() { | |
CELLS.forEach(cell => { | |
// cell.setAttribute('role', 'gridcell'); | |
cell.setAttribute('aria-selected', 'false'); | |
cell.setAttribute('tabindex', -1); | |
}); | |
} | |
const CELL_TODAY = [...CELLS].find(el => parseInt(el.textContent) === DT_TODAY.getDate()); | |
const CELL_SELECTED = [...CELLS].find(el => parseInt(el.textContent) === DT_SELECTED.getDate()); | |
CELL_TODAY.setAttribute('aria-current', 'date'); | |
CELL_TODAY.title = 'current'; | |
CELL_SELECTED.setAttribute('aria-selected', 'true'); | |
CELL_SELECTED.setAttribute('tabindex', 0); | |
GRID.addEventListener('click', ev => { | |
const DATE = ev.target.textContent; | |
reset(); | |
ev.target.setAttribute('aria-selected', true); | |
ev.target.setAttribute('tabindex', 0); | |
console.debug('click:', DATE, ev); | |
}); | |
const HORIZ = { | |
Left: -1, | |
Right: 1, | |
Up: 0, | |
Down: 0 | |
}; | |
const VERT = { | |
Left: 0, | |
Right: 0, | |
Up: -1, | |
Down: 1 | |
}; | |
GRID.addEventListener('keyup', ev => { | |
const COL = parseInt(ev.target.dataset.col); | |
const WEEK = parseInt(ev.target.parentElement.dataset.week); | |
const BEFORE = ev.target.textContent; | |
const IS_ARROW = /Arrow/.test(ev.key); | |
const DIR = ev.key.replace(/Arrow/, ''); | |
if (IS_ARROW) { | |
ev.preventDefault(); | |
const COORDS = `${COL + HORIZ[DIR]},${WEEK + VERT[DIR]}`; | |
const CELL = GRID.querySelector(`[data-coord="${COORDS}"]`); | |
if (CELL) { | |
reset(); | |
CELL.setAttribute('aria-selected', true); | |
CELL.setAttribute('tabindex', 0); | |
CELL.focus(); | |
} | |
// AFTER = BEFORE + step; | |
console.debug(`keyup: "${COORDS}"`, COL, WEEK, IS_ARROW, DIR, ev); | |
} | |
}); | |
console.debug('My date picker:', DT_TODAY, MONTH, PICKER); | |
</script> | |
<pre> | |
NDF, 08-June-2022. | |
</pre> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment