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