mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-22 06:30:59 +01:00
Use flatpickr for date widget calendar
This commit is contained in:
parent
5ff64259ee
commit
8203eb3efd
@ -3,8 +3,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {css} from "@lion/core";
|
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 {
|
:host {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@ -13,4 +17,27 @@ export const dateStyles = css`
|
|||||||
.overdue {
|
.overdue {
|
||||||
color: red; // var(--whatever the theme color)
|
color: red; // var(--whatever the theme color)
|
||||||
}
|
}
|
||||||
`;
|
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);
|
||||||
|
@ -9,11 +9,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import {css, html} from "@lion/core";
|
import {css} from "@lion/core";
|
||||||
import {LionInputDatepicker} from "@lion/input-datepicker";
|
import 'lit-flatpickr';
|
||||||
import {Unparseable} from "@lion/form-core";
|
|
||||||
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
|
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
|
||||||
import {dateStyles} from "./DateStyles";
|
import {dateStyles} from "./DateStyles";
|
||||||
|
import {LitFlatpickr} from "lit-flatpickr";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,6 +43,7 @@ export function parseDate(dateString)
|
|||||||
}
|
}
|
||||||
|
|
||||||
let formatString = <string>(window.egw.preference("dateformat") || 'Y-m-d');
|
let formatString = <string>(window.egw.preference("dateformat") || 'Y-m-d');
|
||||||
|
//@ts-ignore replaceAll() does not exist
|
||||||
formatString = formatString.replaceAll(new RegExp('[-/\.]', 'ig'), '-');
|
formatString = formatString.replaceAll(new RegExp('[-/\.]', 'ig'), '-');
|
||||||
let parsedString = "";
|
let parsedString = "";
|
||||||
switch(formatString)
|
switch(formatString)
|
||||||
@ -204,17 +205,14 @@ export function formatDate(date : Date, options = {dateFormat: ""}) : string
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
let _value = '';
|
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 || <string>window.egw.preference("dateformat") || 'Y-m-d';
|
let dateformat = options.dateFormat || <string>window.egw.preference("dateformat") || 'Y-m-d';
|
||||||
|
|
||||||
var replace_map = {
|
let replace_map = {
|
||||||
d: (date.getUTCDate() < 10 ? "0" : "") + date.getUTCDate(),
|
d: (date.getUTCDate() < 10 ? "0" : "") + date.getUTCDate(),
|
||||||
m: (date.getUTCMonth() < 9 ? "0" : "") + (date.getUTCMonth() + 1),
|
m: (date.getUTCMonth() < 9 ? "0" : "") + (date.getUTCMonth() + 1),
|
||||||
Y: "" + date.getUTCFullYear()
|
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)
|
_value = dateformat.replace(re, function(matched)
|
||||||
{
|
{
|
||||||
return replace_map[matched];
|
return replace_map[matched];
|
||||||
@ -270,22 +268,16 @@ export function formatDateTime(date : Date, options = {dateFormat: "", timeForma
|
|||||||
return formatDate(date, options) + " " + formatTime(date, options);
|
return formatDate(date, options) + " " + formatTime(date, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Et2Date extends Et2InputWidget(LionInputDatepicker)
|
export class Et2Date extends Et2InputWidget(LitFlatpickr)
|
||||||
{
|
{
|
||||||
static get styles()
|
static get styles()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
...super.styles,
|
...super.styles,
|
||||||
dateStyles,
|
...dateStyles,
|
||||||
css`
|
css`
|
||||||
:host([focused]) ::slotted(button), :host(:hover) ::slotted(button) {
|
:host {
|
||||||
display: inline-block;
|
width: auto;
|
||||||
}
|
|
||||||
::slotted(.calendar_button) {
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
margin-left: -20px;
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
@ -301,36 +293,45 @@ export class Et2Date extends Et2InputWidget(LionInputDatepicker)
|
|||||||
constructor()
|
constructor()
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
this.parser = parseDate;
|
|
||||||
this.formatter = formatDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback()
|
// Override some flatpickr defaults how we like it
|
||||||
{
|
this.altFormat = this.egw().preference("dateformat") || "Y-m-d";
|
||||||
super.connectedCallback();
|
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<void>}
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line class-methods-use-this
|
async init()
|
||||||
serializer(modelValue : Date)
|
|
||||||
{
|
{
|
||||||
// isValidDate() is hidden inside LionInputDate, and not exported
|
if(this.locale)
|
||||||
// @ts-ignore Can't call isNan(Date), but we're just checking
|
|
||||||
if(!(modelValue instanceof Date) || isNaN(modelValue))
|
|
||||||
{
|
{
|
||||||
return '';
|
// await loadLocale(this.locale);
|
||||||
}
|
}
|
||||||
// modelValue is localized, so we take the timezone offset in milliseconds and subtract it
|
this.initializeComponent();
|
||||||
// before converting it to ISO string.
|
|
||||||
const offset = modelValue.getTimezoneOffset() * 60000;
|
|
||||||
return new Date(modelValue.getTime() - offset).toJSON().replace(/\.\d{3}Z$/, 'Z');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set_value(value)
|
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()
|
getValue()
|
||||||
@ -340,108 +341,20 @@ export class Et2Date extends Et2InputWidget(LionInputDatepicker)
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copied from flatpickr, since Et2InputWidget overwrote flatpickr.getValue()
|
||||||
|
if(!this._inputElement)
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
let value = this._inputElement.value;
|
||||||
|
|
||||||
// Empty field, return ''
|
// Empty field, return ''
|
||||||
if(!this.modelValue)
|
if(!value)
|
||||||
{
|
{
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// The supplied value was not understandable, return null
|
return value;
|
||||||
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`
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="calendar_button"
|
|
||||||
@click="${this.__openCalendarOverlay}"
|
|
||||||
id="${this.__invokerId}"
|
|
||||||
aria-label="${this.msgLit('lion-input-datepicker:openDatepickerLabel')}"
|
|
||||||
title="${this.msgLit('lion-input-datepicker:openDatepickerLabel')}"
|
|
||||||
>
|
|
||||||
<img src="${img}" style="width:16px"/>
|
|
||||||
</button>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,9 +9,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import {css, html} from "@lion/core";
|
import {css} from "@lion/core";
|
||||||
import {Et2Date, formatDateTime, parseDateTime} from "./Et2Date";
|
import {Et2Date} from "./Et2Date";
|
||||||
import {Unparseable} from "@lion/form-core";
|
|
||||||
|
|
||||||
|
|
||||||
export class Et2DateTime extends Et2Date
|
export class Et2DateTime extends Et2Date
|
||||||
@ -44,24 +43,15 @@ export class Et2DateTime extends Et2Date
|
|||||||
constructor()
|
constructor()
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
this.parser = parseDateTime;
|
|
||||||
this.formatter = formatDateTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
getValue()
|
// Configure flatpickr
|
||||||
{
|
let dateFormat = (this.egw().preference("dateformat") || "Y-m-d");
|
||||||
if(this.readOnly)
|
let timeFormat = ((<string>window.egw.preference("timeformat") || "24") == "24" ? "H:i" : "h:i K");
|
||||||
{
|
this.altFormat = dateFormat + " " + timeFormat;
|
||||||
return null;
|
this.enableTime = true;
|
||||||
}
|
this.time_24hr = this.egw().preference("timeformat", "common") == "24";
|
||||||
|
this.dateFormat = "Y-m-dTH:i:00\\Z";
|
||||||
// The supplied value was not understandable, return null
|
this.defaultHour = new Date().getHours();
|
||||||
if(this.modelValue instanceof Unparseable || !this.modelValue)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.modelValue.toJSON();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,5 +283,5 @@ class Date extends Transformer
|
|||||||
}
|
}
|
||||||
|
|
||||||
\EGroupware\Api\Etemplate\Widget::registerWidget(__NAMESPACE__ . '\\Date',
|
\EGroupware\Api\Etemplate\Widget::registerWidget(__NAMESPACE__ . '\\Date',
|
||||||
array('et2-date', 'et2-datetime', 'time_or_date')
|
array('et2-date', 'et2-date-time', 'time_or_date')
|
||||||
);
|
);
|
@ -63,6 +63,7 @@
|
|||||||
"@lion/select": "^0.14.7",
|
"@lion/select": "^0.14.7",
|
||||||
"@lion/textarea": "^0.13.4",
|
"@lion/textarea": "^0.13.4",
|
||||||
"jquery-ui-timepicker-addon": "^1.6.3",
|
"jquery-ui-timepicker-addon": "^1.6.3",
|
||||||
|
"lit-flatpickr": "^0.3.0",
|
||||||
"sortablejs": "^1.14.0"
|
"sortablejs": "^1.14.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
Loading…
Reference in New Issue
Block a user