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 {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)
|
||||
}
|
||||
`;
|
||||
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 {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 = <string>(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 || <string>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<void>}
|
||||
*/
|
||||
// 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`
|
||||
<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>
|
||||
`;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = ((<string>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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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')
|
||||
);
|
|
@ -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": {
|
||||
|
|
Loading…
Reference in New Issue