<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);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.