Lot of fixes

This commit is contained in:
trading_peter 2023-01-27 12:46:53 +01:00
parent 774b682936
commit 49025783ed
2 changed files with 106 additions and 41 deletions

View File

@ -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": {

View File

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