From 8203eb3efd5d08ac60cabf7a41934c32197c888a Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 15 Feb 2022 11:47:42 -0700 Subject: [PATCH] Use flatpickr for date widget calendar --- api/js/etemplate/Et2Date/DateStyles.ts | 31 +++- api/js/etemplate/Et2Date/Et2Date.ts | 179 ++++++------------------ api/js/etemplate/Et2Date/Et2DateTime.ts | 30 ++-- api/src/Etemplate/Widget/Date.php | 2 +- package.json | 1 + 5 files changed, 87 insertions(+), 156 deletions(-) diff --git a/api/js/etemplate/Et2Date/DateStyles.ts b/api/js/etemplate/Et2Date/DateStyles.ts index ee55eed78c..63958964f4 100644 --- a/api/js/etemplate/Et2Date/DateStyles.ts +++ b/api/js/etemplate/Et2Date/DateStyles.ts @@ -3,8 +3,12 @@ */ import {css} from "@lion/core"; +import {colorsDefStyles} from "../Styles/colorsDefStyles"; +import {cssImage} from "../Et2Widget/Et2Widget"; -export const dateStyles = css` +export const dateStyles = [ + colorsDefStyles, + css` :host { display: inline-block; white-space: nowrap; @@ -13,4 +17,27 @@ export const dateStyles = css` .overdue { color: red; // var(--whatever the theme color) } -`; \ No newline at end of file + input.flatpickr-input { + border: 1px solid; + border-color: var(--input-border-color); + color: var(--input-text-color); + padding-top: 4px; + padding-bottom: 4px; + } + input.flatpickr-input:hover { + background-image: ${cssImage("datepopup")}; + background-repeat: no-repeat; + background-position-x: right; + background-position-y: 1px; + background-size: 18px; + } +`]; + +// The lit-flatpickr package uses a CDN, in violation of best practices +// I've downloaded it +const themeUrl = "api/js/etemplate/Et2Date/flatpickr_material_blue.css"; +const styleElem = document.createElement('link'); +styleElem.rel = 'stylesheet'; +styleElem.type = 'text/css'; +styleElem.href = themeUrl; +document.head.append(styleElem); diff --git a/api/js/etemplate/Et2Date/Et2Date.ts b/api/js/etemplate/Et2Date/Et2Date.ts index 1496ba7d55..dde9c315ec 100644 --- a/api/js/etemplate/Et2Date/Et2Date.ts +++ b/api/js/etemplate/Et2Date/Et2Date.ts @@ -9,11 +9,11 @@ */ -import {css, html} from "@lion/core"; -import {LionInputDatepicker} from "@lion/input-datepicker"; -import {Unparseable} from "@lion/form-core"; +import {css} from "@lion/core"; +import 'lit-flatpickr'; import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; import {dateStyles} from "./DateStyles"; +import {LitFlatpickr} from "lit-flatpickr"; /** @@ -43,6 +43,7 @@ export function parseDate(dateString) } let formatString = (window.egw.preference("dateformat") || 'Y-m-d'); + //@ts-ignore replaceAll() does not exist formatString = formatString.replaceAll(new RegExp('[-/\.]', 'ig'), '-'); let parsedString = ""; switch(formatString) @@ -204,17 +205,14 @@ export function formatDate(date : Date, options = {dateFormat: ""}) : string return ""; } let _value = ''; - // Add timezone offset back in, or formatDate will lose those hours - let formatDate = new Date(date.valueOf() - date.getTimezoneOffset() * 60 * 1000); - let dateformat = options.dateFormat || window.egw.preference("dateformat") || 'Y-m-d'; - var replace_map = { + let replace_map = { d: (date.getUTCDate() < 10 ? "0" : "") + date.getUTCDate(), m: (date.getUTCMonth() < 9 ? "0" : "") + (date.getUTCMonth() + 1), Y: "" + date.getUTCFullYear() } - var re = new RegExp(Object.keys(replace_map).join("|"), "gi"); + let re = new RegExp(Object.keys(replace_map).join("|"), "gi"); _value = dateformat.replace(re, function(matched) { return replace_map[matched]; @@ -270,22 +268,16 @@ export function formatDateTime(date : Date, options = {dateFormat: "", timeForma return formatDate(date, options) + " " + formatTime(date, options); } -export class Et2Date extends Et2InputWidget(LionInputDatepicker) +export class Et2Date extends Et2InputWidget(LitFlatpickr) { static get styles() { return [ ...super.styles, - dateStyles, + ...dateStyles, css` - :host([focused]) ::slotted(button), :host(:hover) ::slotted(button) { - display: inline-block; - } - ::slotted(.calendar_button) { - border: none; - background: transparent; - margin-left: -20px; - display: none; + :host { + width: auto; } `, ]; @@ -301,36 +293,45 @@ export class Et2Date extends Et2InputWidget(LionInputDatepicker) constructor() { super(); - this.parser = parseDate; - this.formatter = formatDate; - } - connectedCallback() - { - super.connectedCallback(); + // Override some flatpickr defaults how we like it + this.altFormat = this.egw().preference("dateformat") || "Y-m-d"; + this.altInput = true; + this.allowInput = true; + this.dateFormat = "Y-m-d\T00:00:00\Z"; + this.weekNumbers = true; } /** - * @param {Date} modelValue + * Override parent to skip call to CDN + * @returns {Promise} */ - // eslint-disable-next-line class-methods-use-this - serializer(modelValue : Date) + async init() { - // isValidDate() is hidden inside LionInputDate, and not exported - // @ts-ignore Can't call isNan(Date), but we're just checking - if(!(modelValue instanceof Date) || isNaN(modelValue)) + if(this.locale) { - return ''; + // await loadLocale(this.locale); } - // modelValue is localized, so we take the timezone offset in milliseconds and subtract it - // before converting it to ISO string. - const offset = modelValue.getTimezoneOffset() * 60000; - return new Date(modelValue.getTime() - offset).toJSON().replace(/\.\d{3}Z$/, 'Z'); + this.initializeComponent(); } set_value(value) { - this.modelValue = this.parser(value); + if(!value || value == 0 || value == "0") + { + value = ''; + } + // Handle timezone offset, flatpickr uses local time + let date = new Date(value); + let formatDate = new Date(date.valueOf() + date.getTimezoneOffset() * 60 * 1000); + if(!this._instance) + { + this.defaultDate = formatDate; + } + else + { + this.setDate(formatDate); + } } getValue() @@ -340,108 +341,20 @@ export class Et2Date extends Et2InputWidget(LionInputDatepicker) return null; } + // Copied from flatpickr, since Et2InputWidget overwrote flatpickr.getValue() + if(!this._inputElement) + { + return ''; + } + let value = this._inputElement.value; + // Empty field, return '' - if(!this.modelValue) + if(!value) { return ''; } - // The supplied value was not understandable, return null - if(this.modelValue instanceof Unparseable || !this.modelValue) - { - return null; - } - this.modelValue.setUTCHours(0); - this.modelValue.setUTCMinutes(0); - this.modelValue.setSeconds(0, 0); - - return this.modelValue.toJSON(); - } - - get _overlayReferenceNode() - { - return this.getInputNode(); - } - - /** - * @override Configures OverlayMixin - * @desc overrides default configuration options for this component - * @returns {Object} - */ - _defineOverlayConfig() - { - this.hasArrow = false; - if(window.innerWidth >= 600) - { - return { - hidesOnOutsideClick: true, - placementMode: 'local', - popperConfig: { - placement: 'bottom-end', - }, - }; - } - return super.withBottomSheetConfig(); - } - - /** - * The LionCalendar shouldn't know anything about the modelValue; - * it can't handle Unparseable dates, but does handle 'undefined' - * @param {?} modelValue - * @returns {Date|undefined} a 'guarded' modelValue - */ - static __getSyncDownValue(modelValue) - { - if(!(modelValue instanceof Date)) - { - return undefined; - } - const offset = modelValue.getTimezoneOffset() * 60000; - return new Date(modelValue.getTime() + offset); - } - - /** - * Overriding from parent for read-only - * - * @return {TemplateResult} - * @protected - */ - // eslint-disable-next-line class-methods-use-this - _inputGroupInputTemplate() - { - if(this.readOnly) - { - return this.formattedValue; - } - else - { - return super._inputGroupInputTemplate(); - } - } - - /** - * Overriding parent to add class to button, and use an image instead of unicode emoji - */ - // eslint-disable-next-line class-methods-use-this - _invokerTemplate() - { - if(this.readOnly) - { - return ''; - } - let img = this.egw() ? this.egw().image("calendar") || '' : ''; - return html` - - `; + return value; } } diff --git a/api/js/etemplate/Et2Date/Et2DateTime.ts b/api/js/etemplate/Et2Date/Et2DateTime.ts index be0b650c90..b1601ccd11 100644 --- a/api/js/etemplate/Et2Date/Et2DateTime.ts +++ b/api/js/etemplate/Et2Date/Et2DateTime.ts @@ -9,9 +9,8 @@ */ -import {css, html} from "@lion/core"; -import {Et2Date, formatDateTime, parseDateTime} from "./Et2Date"; -import {Unparseable} from "@lion/form-core"; +import {css} from "@lion/core"; +import {Et2Date} from "./Et2Date"; export class Et2DateTime extends Et2Date @@ -44,24 +43,15 @@ export class Et2DateTime extends Et2Date constructor() { super(); - this.parser = parseDateTime; - this.formatter = formatDateTime; - } - getValue() - { - if(this.readOnly) - { - return null; - } - - // The supplied value was not understandable, return null - if(this.modelValue instanceof Unparseable || !this.modelValue) - { - return null; - } - - return this.modelValue.toJSON(); + // Configure flatpickr + let dateFormat = (this.egw().preference("dateformat") || "Y-m-d"); + let timeFormat = ((window.egw.preference("timeformat") || "24") == "24" ? "H:i" : "h:i K"); + this.altFormat = dateFormat + " " + timeFormat; + this.enableTime = true; + this.time_24hr = this.egw().preference("timeformat", "common") == "24"; + this.dateFormat = "Y-m-dTH:i:00\\Z"; + this.defaultHour = new Date().getHours(); } } diff --git a/api/src/Etemplate/Widget/Date.php b/api/src/Etemplate/Widget/Date.php index 2df405aad2..73dc0671c5 100644 --- a/api/src/Etemplate/Widget/Date.php +++ b/api/src/Etemplate/Widget/Date.php @@ -283,5 +283,5 @@ class Date extends Transformer } \EGroupware\Api\Etemplate\Widget::registerWidget(__NAMESPACE__ . '\\Date', - array('et2-date', 'et2-datetime', 'time_or_date') + array('et2-date', 'et2-date-time', 'time_or_date') ); \ No newline at end of file diff --git a/package.json b/package.json index 1cd7c3011d..f14331e967 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "@lion/select": "^0.14.7", "@lion/textarea": "^0.13.4", "jquery-ui-timepicker-addon": "^1.6.3", + "lit-flatpickr": "^0.3.0", "sortablejs": "^1.14.0" }, "engines": {