Lot of fixes
This commit is contained in:
		@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "@tp/tp-date-input",
 | 
			
		||||
  "version": "0.1.0",
 | 
			
		||||
  "version": "0.2.0",
 | 
			
		||||
  "description": "",
 | 
			
		||||
  "main": "tp-date-input.js",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										145
									
								
								tp-date-input.js
									
									
									
									
									
								
							
							
						
						
									
										145
									
								
								tp-date-input.js
									
									
									
									
									
								
							@@ -10,6 +10,8 @@ import { EventHelpers } from '@tp/helpers/event-helpers.js';
 | 
			
		||||
import { FormElement } from '@tp/helpers/form-element.js';
 | 
			
		||||
import { LitElement, html, css } from 'lit';
 | 
			
		||||
import { format, parse, parseISO, isAfter, isValid, endOfDay } from 'date-fns/esm';
 | 
			
		||||
import { zonedTimeToUtc } from 'date-fns-tz/esm';
 | 
			
		||||
import { closest } from '@tp/helpers';
 | 
			
		||||
 | 
			
		||||
class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) {
 | 
			
		||||
  static get styles() {
 | 
			
		||||
@@ -17,15 +19,16 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) {
 | 
			
		||||
      css`
 | 
			
		||||
        :host {
 | 
			
		||||
          display: block;
 | 
			
		||||
          position: relative;
 | 
			
		||||
          font-size: 14px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .wrap {
 | 
			
		||||
          display: flex;
 | 
			
		||||
          flex-direction: column;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .wrap > div {
 | 
			
		||||
          align-self: flex-end;
 | 
			
		||||
          flex-direction: row;
 | 
			
		||||
          align-items: center;
 | 
			
		||||
          border-radius: 2px;
 | 
			
		||||
          border: solid 1px #000;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        tp-input {
 | 
			
		||||
@@ -34,49 +37,73 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) {
 | 
			
		||||
          border: none;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        tp-input.bigger {
 | 
			
		||||
        tp-input.year {
 | 
			
		||||
          width: 50px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        tp-input::part(wrap) {
 | 
			
		||||
          border: none;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .under {
 | 
			
		||||
          position: relative;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .error-message {
 | 
			
		||||
          position: absolute;
 | 
			
		||||
          top: -5px;
 | 
			
		||||
          z-index: 1;
 | 
			
		||||
          left: 0;
 | 
			
		||||
          right: 0;
 | 
			
		||||
          font-size: 10px;
 | 
			
		||||
          color: var(--tp-input-text-color-invalid, #B71C1C);
 | 
			
		||||
          transition: opacity 0.3s;
 | 
			
		||||
          opacity: 0;
 | 
			
		||||
          will-change: opacity;
 | 
			
		||||
          overflow: hidden;
 | 
			
		||||
          text-overflow: ellipsis;
 | 
			
		||||
          white-space: nowrap;
 | 
			
		||||
          pointer-events: none;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        :host([invalid]) .error-message {
 | 
			
		||||
          opacity: 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        div[part="delimiter"] {
 | 
			
		||||
          padding-bottom: 2px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .more {
 | 
			
		||||
          flex: 1;
 | 
			
		||||
          display: flex;
 | 
			
		||||
          justify-content: end;
 | 
			
		||||
        }
 | 
			
		||||
      `
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    const { _delemiter } = this;
 | 
			
		||||
    const { delimiter, autoValidate, readonly, required, errorMessage } = this;
 | 
			
		||||
 | 
			
		||||
    return html`
 | 
			
		||||
      <div class="wrap">
 | 
			
		||||
        <tp-input .value=${this._input0} @change=${this._inputChanged} .validator=${} allowed-pattern="[0-9]" .auto-validate=${autoValidate} .readonly=${readonly} .required=${required}>
 | 
			
		||||
      <div class="wrap" part="wrap">
 | 
			
		||||
        <tp-input part="input ${this._setClass(0)}" exportparts="wrap:innerwrap" class=${this._setClass(0)} .value=${this._input0} @change=${this._inputChanged} .validator=${this._setValidator(0)} .autoValidate=${autoValidate} .readonly=${readonly} .required=${required}>
 | 
			
		||||
          <input type="text" placeholder=${this._setPlaceholder(0)}>
 | 
			
		||||
        </tp-input>
 | 
			
		||||
        <div>${_delemiter}</div>
 | 
			
		||||
        <tp-input .value=${this._input1} @change=${this._inputChanged} .validator=${} allowed-pattern="[0-9]" .auto-validate=${autoValidate} .readonly=${readonly} .required=${required}>
 | 
			
		||||
        <div part="delimiter">${delimiter}</div>
 | 
			
		||||
        <tp-input part="input ${this._setClass(1)}" exportparts="wrap:innerwrap" class=${this._setClass(1)} .value=${this._input1} @change=${this._inputChanged} .validator=${this._setValidator(1)} .autoValidate=${autoValidate} .readonly=${readonly} .required=${required}>
 | 
			
		||||
          <input type="text" placeholder=${this._setPlaceholder(1)}>
 | 
			
		||||
        </tp-input>
 | 
			
		||||
        <div>${_delemiter}</div>
 | 
			
		||||
        <tp-input class="bigger" .value=${this._input2} @change=${this._inputChanged} .validator=${} allowed-pattern="[0-9]" .auto-validate=${autoValidate} .readonly=${readonly} .required=${required}>
 | 
			
		||||
        <div part="delimiter">${delimiter}</div>
 | 
			
		||||
        <tp-input part="input ${this._setClass(2)}" exportparts="wrap:innerwrap" class=${this._setClass(2)} .value=${this._input2} @change=${this._inputChanged} .validator=${this._setValidator(2)} .autoValidate=${autoValidate} .readonly=${readonly} .required=${required}>
 | 
			
		||||
          <input type="text" placeholder=${this._setPlaceholder(2)}>
 | 
			
		||||
        </tp-input>
 | 
			
		||||
        <div class="more">
 | 
			
		||||
          <slot></slot>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      ${errorMessage ? html`
 | 
			
		||||
        <div class="error-message" part="error-message">${errorMessage}</div>
 | 
			
		||||
      ` : null}
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -115,6 +142,13 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) {
 | 
			
		||||
      // Set it to `today` to automatically allow dates till today (inclusive).
 | 
			
		||||
      // Expects 'today' or a ISO Date string.
 | 
			
		||||
      maxDate: { type: String },
 | 
			
		||||
 | 
			
		||||
      // List of allowed dates. Any other dates will make the validation fail.
 | 
			
		||||
      allowedDates: { type: Array },
 | 
			
		||||
 | 
			
		||||
      timeZone: { type: String },
 | 
			
		||||
 | 
			
		||||
      delimiter: { type: String },
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -127,6 +161,8 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) {
 | 
			
		||||
    this.optional = false;
 | 
			
		||||
    this.maxYear = 10;
 | 
			
		||||
    this.minYear = 10;
 | 
			
		||||
    this.allowedDates = [];
 | 
			
		||||
    this._formatChanged();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  shouldUpdate(changes) {
 | 
			
		||||
@@ -142,7 +178,24 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  firstUpdated() {
 | 
			
		||||
    super.firstUpdated();
 | 
			
		||||
    this.listen(this, 'input', '_autoMoveCursor');
 | 
			
		||||
 | 
			
		||||
    const datepicker = this.querySelector('tp-datepicker');
 | 
			
		||||
    if (datepicker) {
 | 
			
		||||
      datepicker.addEventListener('value-changed', e => {
 | 
			
		||||
        this.value = e.detail;
 | 
			
		||||
        const popup = closest(datepicker, 'tp-popup');
 | 
			
		||||
 | 
			
		||||
        if (popup) {
 | 
			
		||||
          popup.close();
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get inputs() {
 | 
			
		||||
    return this.shadowRoot.querySelectorAll('tp-input');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@@ -176,19 +229,16 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * Test is a date is selectable when considering all restricting options
 | 
			
		||||
  * of the control, like enabledDates, maxDate, ...
 | 
			
		||||
  * of the control, like allowedDates, maxDate, ...
 | 
			
		||||
  */
 | 
			
		||||
  dateValid(date, maxDate, enabledDates) {
 | 
			
		||||
  dateValid(date, maxDate = this.maxDate, allowedDates = this.allowedDates) {
 | 
			
		||||
    date = this._toDate(date);
 | 
			
		||||
 | 
			
		||||
    if (isValid(date) === false) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    maxDate = maxDate || this.maxDate;
 | 
			
		||||
    enabledDates = enabledDates || [];
 | 
			
		||||
 | 
			
		||||
    if ((enabledDates.length > 0 && enabledDates.indexOf(date) === -1) ||
 | 
			
		||||
    if ((allowedDates.length > 0 && allowedDates.indexOf(date) === -1) ||
 | 
			
		||||
      (maxDate && isAfter(date, maxDate))) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
@@ -202,7 +252,7 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) {
 | 
			
		||||
    this._input0 = '';
 | 
			
		||||
    this._input1 = '';
 | 
			
		||||
    this._input2 = '';
 | 
			
		||||
    this.shadowRoot.querySelectorAll('era-input').forEach(el => el.invalid = false);
 | 
			
		||||
    this.inputs.forEach(el => el.invalid = false);
 | 
			
		||||
    this.invalid = false;
 | 
			
		||||
    this.value = null;
 | 
			
		||||
 | 
			
		||||
@@ -212,9 +262,14 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _inputChanged() {
 | 
			
		||||
    const i0 = this._input0;
 | 
			
		||||
    const i1 = this._input1;
 | 
			
		||||
    const i2 = this._input2;
 | 
			
		||||
    this._skipOnValueChanged = true;
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      this._skipOnValueChanged = false;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const i0 = this.inputs[0].value;
 | 
			
		||||
    const i1 = this.inputs[1].value;
 | 
			
		||||
    const i2 = this.inputs[2].value;
 | 
			
		||||
 | 
			
		||||
    if (i0 === '' && i1 === '' && i2 === '' && this.optional) {
 | 
			
		||||
      this.date = null;
 | 
			
		||||
@@ -225,7 +280,7 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) {
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
      i0 === '' || i1 === '' || i2 === '' ||
 | 
			
		||||
      !this.inputs[0].validate() || !this.inputs[1].validate() || !this.inputs[2].validate()
 | 
			
		||||
      !(this.inputs[0].validate() && this.inputs[1].validate() && this.inputs[2].validate())
 | 
			
		||||
    ) {
 | 
			
		||||
      if (this.focused) {
 | 
			
		||||
        this.date = null;
 | 
			
		||||
@@ -243,9 +298,10 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) {
 | 
			
		||||
      this.inputs[0].invalid = false;
 | 
			
		||||
      this.inputs[1].invalid = false;
 | 
			
		||||
      this.inputs[2].invalid = false;
 | 
			
		||||
      this.date = date;
 | 
			
		||||
      this.value = date.toISOString();
 | 
			
		||||
      this.date = this.timeZone ? zonedTimeToUtc(date, this.timeZone) : date;
 | 
			
		||||
      this.value = this.date.toISOString();
 | 
			
		||||
      this.invalid = false;
 | 
			
		||||
      this.dispatchEvent(new CustomEvent('value-changed', { detail: this.value, bubbles: true, composed: true }));
 | 
			
		||||
    } else {
 | 
			
		||||
      this.inputs[0].invalid = true;
 | 
			
		||||
      this.inputs[1].invalid = true;
 | 
			
		||||
@@ -269,7 +325,7 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _validateDay(value) {
 | 
			
		||||
  _validateDay(el, value) {
 | 
			
		||||
    if (typeof value !== 'string') {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
@@ -282,7 +338,7 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) {
 | 
			
		||||
    return v >= 1 && v <= 31;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _validateMonth(value) {
 | 
			
		||||
  _validateMonth(el, value) {
 | 
			
		||||
    if (typeof value !== 'string') {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
@@ -295,7 +351,7 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) {
 | 
			
		||||
    return v >= 1 && v <= 12;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _validateYear(value) {
 | 
			
		||||
  _validateYear(el, value) {
 | 
			
		||||
    if (typeof value !== 'string') {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
@@ -319,17 +375,27 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _formatChanged(format) {
 | 
			
		||||
    if (!format) return;
 | 
			
		||||
  _setClass(idx) {
 | 
			
		||||
    switch (this._inputAssign[idx]) {
 | 
			
		||||
      case 'dd':
 | 
			
		||||
      case 'MM':
 | 
			
		||||
        return '';
 | 
			
		||||
      case 'y':
 | 
			
		||||
        return 'year';
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    let _realFormat = format;
 | 
			
		||||
  _formatChanged() {
 | 
			
		||||
    if (!this.format) return;
 | 
			
		||||
 | 
			
		||||
    let _realFormat = this.format;
 | 
			
		||||
 | 
			
		||||
    const types = ['MM', 'dd', 'y'];
 | 
			
		||||
    this._inputAssign = [];
 | 
			
		||||
 | 
			
		||||
    this._delimiter = this._determineDelimiter(_realFormat);
 | 
			
		||||
    this.delimiter = this._determineDelimiter(_realFormat);
 | 
			
		||||
 | 
			
		||||
    const parts = _realFormat.split(this._delimiter);
 | 
			
		||||
    const parts = _realFormat.split(this.delimiter);
 | 
			
		||||
    if (parts.length < 2) {
 | 
			
		||||
      console.warn(this.tagName + ': Unknown format. Fallback to format MM-dd-y');
 | 
			
		||||
      this.format = 'MM-dd-y';
 | 
			
		||||
@@ -366,9 +432,8 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) {
 | 
			
		||||
  // Reset invalid state if value was changed.
 | 
			
		||||
  // This clears up old invalid states if the value was changed programmatically.
 | 
			
		||||
  _onValueChanged() {
 | 
			
		||||
    // If the control is focused we ignore a programmatically set value because
 | 
			
		||||
    // the user may works with the element right now.
 | 
			
		||||
    if (this.focused) {
 | 
			
		||||
    // We skip if the user was just inputting values.
 | 
			
		||||
    if (this._skipOnValueChanged) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -409,7 +474,7 @@ class TpDateInput extends EventHelpers(ControlState(FormElement(LitElement))) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _autoMoveCursor(e) {
 | 
			
		||||
    const target = e.composedPath().find(node => node.tagName === 'ERA-INPUT');
 | 
			
		||||
    const target = e.composedPath().find(node => node.tagName === 'TP-INPUT');
 | 
			
		||||
    const idx = Array.from(this.inputs).findIndex(node => node === target);
 | 
			
		||||
    if (target.value.length === 2 && idx < 2) {
 | 
			
		||||
      this.inputs[idx + 1].select();
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user