<template id="time-picker-dom">
<style>
:host {
display: inline-flex;
align-items: center;
padding: 0.5rem;
border-radius: 6px;
background-color: black;
color: white;
}
.field {
display: flex;
flex-direction: column;
}
.field > * {
padding: 2px;
border-radius: 2px;
}
.field > *:hover, .field > *:focus {
background-color: #355cca;
}
input {
font-size: 1.2rem;
width: 2ch;
height: 2ch;
background-color: inherit;
border: none;
color: inherit;
}
svg {
fill: white;
}
.colon::after {
content: ':';
margin: 0.4rem;
color: white;
}
</style>
<div class="field">
<svg class="icon switch" viewBox="0 0 24 24" tabIndex="0"><g><path d="M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z"></path></g></svg>
<input id="hours" type="text" maxlength="2">
<svg class="icon switch" viewBox="0 0 24 24" tabIndex="0"><g><path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"></path></g></svg>
</div>
<span class="colon"></span>
<div class="field">
<svg class="icon switch" viewBox="0 0 24 24" tabIndex="0"><g><path d="M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z"></path></g></svg>
<input id="minutes" type="text" maxlength="2">
<svg class="icon switch" viewBox="0 0 24 24" tabIndex="0"><g><path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"></path></g></svg>
</div>
<span class="colon"></span>
<div class="field">
<svg class="icon switch" viewBox="0 0 24 24" tabIndex="0"><g><path d="M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z"></path></g></svg>
<input id="seconds" type="text" maxlength="2">
<svg class="icon switch" viewBox="0 0 24 24" tabIndex="0"><g><path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"></path></g></svg>
</div>
</template>
<time-picker hours="12" minutes="59" seconds="59"></time-picker>
class TimePicker extends HTMLElement {
static get observedAttributes() {return ['hours', 'minutes', 'seconds']};
constructor() {
super();
this.initShadowDOM();
this.setUIReferences();
this.updateDisplay();
this.addEventListeners();
}
initShadowDOM() {
this.attachShadow({mode: 'open'});
const clockDOM = document.getElementById('time-picker-dom');
const clockDOMClone = document.importNode(clockDOM.content, true);
this.shadowRoot.appendChild(clockDOMClone);
}
setUIReferences() {
const fields = this.shadowRoot.querySelectorAll('.field');
this.hourDisplay = fields[0].querySelector('input');
this.hourIncrement = fields[0].querySelectorAll('svg')[0];
this.hourDecrement = fields[0].querySelectorAll('svg')[1];
this.minuteDisplay = fields[1].querySelector('input');
this.minuteIncrement = fields[1].querySelectorAll('svg')[0];
this.minuteDecrement = fields[1].querySelectorAll('svg')[1];
this.secondDisplay = fields[2].querySelector('input');
this.secondIncrement = fields[2].querySelectorAll('svg')[0];
this.secondDecrement = fields[2].querySelectorAll('svg')[1];
}
set hours(value) {
const hours = parseInt(value);
if (this.validateHours(hours)) {
this.setAttribute('hours', hours);
}
}
set minutes(value) {
const minutes = parseInt(value);
if (this.validateMinutesOrSeconds(minutes)) {
this.setAttribute('minutes', minutes);
}
}
set seconds(value) {
const seconds = parseInt(value);
if (this.validateMinutesOrSeconds(seconds)) {
this.setAttribute('seconds', seconds);
}
}
get hours() {
return parseInt(this.getAttribute('hours'));
}
get minutes() {
return parseInt(this.getAttribute('minutes'));
}
get seconds() {
return parseInt(this.getAttribute('seconds'));
}
addEventListeners() {
this.incrementHour = this.incrementHour.bind(this);
this.decrementHour = this.decrementHour.bind(this);
this.incrementMinute = this.incrementMinute.bind(this);
this.decrementMinute = this.decrementMinute.bind(this);
this.incrementSecond = this.incrementSecond.bind(this);
this.decrementSecond = this.decrementSecond.bind(this);
function executeOnEnter(fn, event) {
if (event.key === 'Enter') {
fn();
}
}
this.hourIncrement.addEventListener('click', this.incrementHour);
this.hourIncrement.addEventListener('keydown', executeOnEnter.bind(this, this.incrementHour));
this.hourDecrement.addEventListener('click', this.decrementHour);
this.hourDecrement.addEventListener('keydown', executeOnEnter.bind(this, this.decrementHour));
this.minuteIncrement.addEventListener('click', this.incrementMinute);
this.minuteIncrement.addEventListener('keydown', executeOnEnter.bind(this, this.incrementMinute));
this.minuteDecrement.addEventListener('click', this.decrementMinute);
this.minuteDecrement.addEventListener('keydown', executeOnEnter.bind(this, this.decrementMinute));
this.secondIncrement.addEventListener('click', this.incrementSecond);
this.secondIncrement.addEventListener('keydown', executeOnEnter.bind(this, this.incrementSecond));
this.secondDecrement.addEventListener('click', this.decrementSecond);
this.secondDecrement.addEventListener('keydown', executeOnEnter.bind(this, this.decrementSecond));
this.hourDisplay.addEventListener('change', event => {
const hours = parseInt(event.currentTarget.value);
if (this.validateHours(hours)) {
this.setAttribute('hours', hours);
} else {
this.updateDisplay();
}
});
this.minuteDisplay.addEventListener('change', event => {
const minutes = parseInt(event.currentTarget.value);
if (this.validateMinutesOrSeconds(minutes)) {
this.setAttribute('minutes', minutes);
} else {
this.updateDisplay();
}
});
this.secondDisplay.addEventListener('change', event => {
const seconds = parseInt(event.currentTarget.value);
if (this.validateMinutesOrSeconds(seconds)) {
this.setAttribute('seconds', seconds);
} else {
this.updateDisplay();
}
});
}
attributeChangedCallback(name, oldValue, newValue) {
if (['hours', 'minutes', 'seconds'].includes(name)) {
this.updateDisplay();
}
}
incrementHour() {
let hours = parseInt(this.getAttribute('hours'));
hours = (hours + 1) % 24;
this.setAttribute('hours', hours);
}
decrementHour() {
let hours = parseInt(this.getAttribute('hours'));
--hours;
if (hours === -1) {
hours = 23;
}
this.setAttribute('hours', hours);
}
incrementMinute() {
let minutes = parseInt(this.getAttribute('minutes'));
minutes = (minutes + 1) % 60;
if (minutes === 0) {
this.incrementHour();
}
this.setAttribute('minutes', minutes);
}
decrementMinute() {
let minutes = parseInt(this.getAttribute('minutes'));
--minutes;
if (minutes === -1) {
minutes = 59;
this.decrementHour();
}
this.setAttribute('minutes', minutes);
}
incrementSecond() {
let seconds = parseInt(this.getAttribute('seconds'));
seconds = (seconds + 1) % 60;
if (seconds === 0) {
this.incrementMinute();
}
this.setAttribute('seconds', seconds);
}
decrementSecond() {
let seconds = parseInt(this.getAttribute('seconds'));
--seconds;
if (seconds === -1) {
seconds = 59;
this.decrementMinute();
}
this.setAttribute('seconds', seconds);
}
updateDisplay() {
this.hourDisplay.value = this.formatClockValue(this.getAttribute('hours'));
this.minuteDisplay.value = this.formatClockValue(this.getAttribute('minutes'));
this.secondDisplay.value = this.formatClockValue(this.getAttribute('seconds'));
}
formatClockValue(value) {
return value.toString().padStart(2, '0');
}
validateHours(value) {
return Number.isInteger(value) && value >= 0 && value < 24;
}
validateMinutesOrSeconds(value) {
return Number.isInteger(value) && value >= 0 && value < 60;
}
get time() {
const nowDate = new Date();
return Date.UTC(nowDate.getFullYear(), nowDate.getMonth(), nowDate.getDate(), this.getAttribute('hours'), this.getAttribute('minutes'), this.getAttribute('seconds'));
}
}
window.customElements.define('time-picker', TimePicker);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.