This commit adds the exports entry in each package.json, exposing the public parts of each package and hiding the private ones.
411 lines
13 KiB
JavaScript
411 lines
13 KiB
JavaScript
import { LionCalendar } from '@lion/calendar';
|
|
import { html, ifDefined, ScopedElementsMixin } from '@lion/core';
|
|
import { LionInputDate } from '@lion/input-date';
|
|
import {
|
|
OverlayMixin,
|
|
withBottomSheetConfig,
|
|
withModalDialogConfig,
|
|
ArrowMixin,
|
|
} from '@lion/overlays';
|
|
import { LionCalendarOverlayFrame } from './LionCalendarOverlayFrame.js';
|
|
|
|
/**
|
|
* @customElement lion-input-datepicker
|
|
*/
|
|
// @ts-expect-error false positive for incompatible static get properties. Lit-element merges super properties already for you.
|
|
export class LionInputDatepicker extends ScopedElementsMixin(
|
|
ArrowMixin(OverlayMixin(LionInputDate)),
|
|
) {
|
|
static get scopedElements() {
|
|
return {
|
|
...super.scopedElements,
|
|
'lion-calendar': LionCalendar,
|
|
'lion-calendar-overlay-frame': LionCalendarOverlayFrame,
|
|
};
|
|
}
|
|
|
|
static get properties() {
|
|
return {
|
|
/**
|
|
* The heading to be added on top of the calendar overlay.
|
|
* Naming chosen from an Application Developer perspective.
|
|
* For a Subclasser 'calendarOverlayHeading' would be more appropriate.
|
|
*/
|
|
calendarHeading: {
|
|
type: String,
|
|
attribute: 'calendar-heading',
|
|
},
|
|
/**
|
|
* The slot to put the invoker button in. Can be 'prefix', 'suffix', 'before' and 'after'.
|
|
* Default will be 'suffix'.
|
|
*/
|
|
_calendarInvokerSlot: {
|
|
attribute: false,
|
|
},
|
|
|
|
__calendarMinDate: {
|
|
attribute: false,
|
|
},
|
|
|
|
__calendarMaxDate: {
|
|
attribute: false,
|
|
},
|
|
|
|
__calendarDisableDates: {
|
|
attribute: false,
|
|
},
|
|
};
|
|
}
|
|
|
|
get slots() {
|
|
return {
|
|
...super.slots,
|
|
[this._calendarInvokerSlot]: () => {
|
|
const renderParent = document.createElement('div');
|
|
/** @type {typeof LionInputDatepicker} */ (this.constructor).render(
|
|
this._invokerTemplate(),
|
|
renderParent,
|
|
{
|
|
scopeName: this.localName,
|
|
eventContext: this,
|
|
},
|
|
);
|
|
return /** @type {HTMLElement} */ (renderParent.firstElementChild);
|
|
},
|
|
};
|
|
}
|
|
|
|
static get localizeNamespaces() {
|
|
return [
|
|
{
|
|
'lion-input-datepicker': /** @param {string} locale */ locale => {
|
|
switch (locale) {
|
|
case 'bg-BG':
|
|
return import('../translations/bg-BG.js');
|
|
case 'bg':
|
|
return import('../translations/bg.js');
|
|
case 'cs-CZ':
|
|
return import('../translations/cs-CZ.js');
|
|
case 'cs':
|
|
return import('../translations/cs.js');
|
|
case 'de-DE':
|
|
return import('../translations/de-DE.js');
|
|
case 'de':
|
|
return import('../translations/de.js');
|
|
case 'en-AU':
|
|
return import('../translations/en-AU.js');
|
|
case 'en-GB':
|
|
return import('../translations/en-GB.js');
|
|
case 'en-US':
|
|
return import('../translations/en-US.js');
|
|
case 'en-PH':
|
|
case 'en':
|
|
return import('../translations/en.js');
|
|
case 'es-ES':
|
|
return import('../translations/es-ES.js');
|
|
case 'es':
|
|
return import('../translations/es.js');
|
|
case 'fr-FR':
|
|
return import('../translations/fr-FR.js');
|
|
case 'fr-BE':
|
|
return import('../translations/fr-BE.js');
|
|
case 'fr':
|
|
return import('../translations/fr.js');
|
|
case 'hu-HU':
|
|
return import('../translations/hu-HU.js');
|
|
case 'hu':
|
|
return import('../translations/hu.js');
|
|
case 'it-IT':
|
|
return import('../translations/it-IT.js');
|
|
case 'it':
|
|
return import('../translations/it.js');
|
|
case 'nl-BE':
|
|
return import('../translations/nl-BE.js');
|
|
case 'nl-NL':
|
|
return import('../translations/nl-NL.js');
|
|
case 'nl':
|
|
return import('../translations/nl.js');
|
|
case 'pl-PL':
|
|
return import('../translations/pl-PL.js');
|
|
case 'pl':
|
|
return import('../translations/pl.js');
|
|
case 'ro-RO':
|
|
return import('../translations/ro-RO.js');
|
|
case 'ro':
|
|
return import('../translations/ro.js');
|
|
case 'ru-RU':
|
|
return import('../translations/ru-RU.js');
|
|
case 'ru':
|
|
return import('../translations/ru.js');
|
|
case 'sk-SK':
|
|
return import('../translations/sk-SK.js');
|
|
case 'sk':
|
|
return import('../translations/sk.js');
|
|
case 'uk-UA':
|
|
return import('../translations/uk-UA.js');
|
|
case 'uk':
|
|
return import('../translations/uk.js');
|
|
case 'zh-CN':
|
|
case 'zh':
|
|
return import('../translations/zh.js');
|
|
default:
|
|
return import('../translations/en.js');
|
|
}
|
|
},
|
|
},
|
|
...super.localizeNamespaces,
|
|
];
|
|
}
|
|
|
|
get _invokerNode() {
|
|
return /** @type {HTMLElement} */ (this.querySelector(`#${this.__invokerId}`));
|
|
}
|
|
|
|
get _calendarNode() {
|
|
return /** @type {LionCalendar} */ (this._overlayCtrl.contentNode.querySelector(
|
|
'[slot="content"]',
|
|
));
|
|
}
|
|
|
|
constructor() {
|
|
super();
|
|
this.__invokerId = this.__createUniqueIdForA11y();
|
|
this._calendarInvokerSlot = 'suffix';
|
|
|
|
// Configuration flags for subclassers
|
|
this._focusCentralDateOnCalendarOpen = true;
|
|
this._hideOnUserSelect = true;
|
|
this._syncOnUserSelect = true;
|
|
|
|
this.__openCalendarOverlay = this.__openCalendarOverlay.bind(this);
|
|
this._onCalendarUserSelectedChanged = this._onCalendarUserSelectedChanged.bind(this);
|
|
}
|
|
|
|
__createUniqueIdForA11y() {
|
|
return `${this.localName}-${Math.random().toString(36).substr(2, 10)}`;
|
|
}
|
|
|
|
/**
|
|
* @param {PropertyKey} name
|
|
* @param {?} oldValue
|
|
*/
|
|
requestUpdateInternal(name, oldValue) {
|
|
super.requestUpdateInternal(name, oldValue);
|
|
|
|
if (name === 'disabled' || name === 'readOnly') {
|
|
this.__toggleInvokerDisabled();
|
|
}
|
|
}
|
|
|
|
__toggleInvokerDisabled() {
|
|
if (this._invokerNode) {
|
|
const invokerNode = /** @type {HTMLElement & {disabled: boolean}} */ (this._invokerNode);
|
|
invokerNode.disabled = this.disabled || this.readOnly;
|
|
}
|
|
}
|
|
|
|
/** @param {import('@lion/core').PropertyValues } changedProperties */
|
|
firstUpdated(changedProperties) {
|
|
super.firstUpdated(changedProperties);
|
|
this.__toggleInvokerDisabled();
|
|
}
|
|
|
|
/** @param {import('@lion/core').PropertyValues } changedProperties */
|
|
updated(changedProperties) {
|
|
super.updated(changedProperties);
|
|
if (changedProperties.has('validators')) {
|
|
const validators = [...(this.validators || [])];
|
|
this.__syncDisabledDates(validators);
|
|
}
|
|
if (changedProperties.has('label')) {
|
|
this.calendarHeading = this.calendarHeading || this.label;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Defining this overlay as a templates from OverlayMixin
|
|
* this is our source to give as .contentNode to OverlayController.
|
|
* Important: do not change the name of this method.
|
|
*/
|
|
_overlayTemplate() {
|
|
// TODO: add performance optimization to only render the calendar if needed
|
|
// When not opened (usually on init), it does not need to be rendered.
|
|
// This would make first paint quicker
|
|
return html`
|
|
<div id="overlay-content-node-wrapper">
|
|
<lion-calendar-overlay-frame class="calendar__overlay-frame">
|
|
<span slot="heading">${this.calendarHeading}</span>
|
|
${this._calendarTemplate()}
|
|
</lion-calendar-overlay-frame>
|
|
${this._arrowNodeTemplate()}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
render() {
|
|
return html`
|
|
<div class="form-field__group-one">${this._groupOneTemplate()}</div>
|
|
<div class="form-field__group-two">
|
|
${this._groupTwoTemplate()} ${this._overlayTemplate()}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Subclassers can replace this with their custom extension of
|
|
* LionCalendar, like `<my-calendar id="calendar"></my-calendar>`
|
|
*/
|
|
// eslint-disable-next-line class-methods-use-this
|
|
_calendarTemplate() {
|
|
return html`
|
|
<lion-calendar
|
|
slot="content"
|
|
.selectedDate="${/** @type {typeof LionInputDatepicker} */ (this
|
|
.constructor).__getSyncDownValue(this.modelValue)}"
|
|
.minDate="${this.__calendarMinDate}"
|
|
.maxDate="${this.__calendarMaxDate}"
|
|
.disableDates="${ifDefined(this.__calendarDisableDates)}"
|
|
@user-selected-date-changed="${this._onCalendarUserSelectedChanged}"
|
|
></lion-calendar>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Subclassers can replace this with their custom extension invoker,
|
|
* like `<my-button><calendar-icon></calendar-icon></my-button>`
|
|
*/
|
|
// eslint-disable-next-line class-methods-use-this
|
|
_invokerTemplate() {
|
|
return html`
|
|
<button
|
|
type="button"
|
|
@click="${this.__openCalendarOverlay}"
|
|
id="${this.__invokerId}"
|
|
aria-label="${this.msgLit('lion-input-datepicker:openDatepickerLabel')}"
|
|
title="${this.msgLit('lion-input-datepicker:openDatepickerLabel')}"
|
|
>
|
|
📅
|
|
</button>
|
|
`;
|
|
}
|
|
|
|
_setupOverlayCtrl() {
|
|
super._setupOverlayCtrl();
|
|
|
|
this.__datepickerBeforeShow = () => {
|
|
this._overlayCtrl.updateConfig(this._defineOverlayConfig());
|
|
};
|
|
this._overlayCtrl.addEventListener('before-show', this.__datepickerBeforeShow);
|
|
}
|
|
|
|
/**
|
|
* @override Configures OverlayMixin
|
|
* @desc overrides default configuration options for this component
|
|
* @returns {Object}
|
|
*/
|
|
// eslint-disable-next-line class-methods-use-this
|
|
_defineOverlayConfig() {
|
|
if (window.innerWidth >= 600) {
|
|
this.hasArrow = true;
|
|
return {
|
|
...withModalDialogConfig(),
|
|
hidesOnOutsideClick: true,
|
|
...super._defineOverlayConfig(),
|
|
popperConfig: {
|
|
...super._defineOverlayConfig().popperConfig,
|
|
placement: 'bottom',
|
|
},
|
|
};
|
|
}
|
|
this.hasArrow = false;
|
|
return withBottomSheetConfig();
|
|
}
|
|
|
|
async __openCalendarOverlay() {
|
|
await this._overlayCtrl.show();
|
|
await Promise.all([
|
|
/** @type {import('@lion/core').LitElement} */ (this._overlayCtrl.contentNode).updateComplete,
|
|
this._calendarNode.updateComplete,
|
|
]);
|
|
this._onCalendarOverlayOpened();
|
|
}
|
|
|
|
/**
|
|
* Lifecycle callback for subclassers
|
|
*/
|
|
_onCalendarOverlayOpened() {
|
|
if (this._focusCentralDateOnCalendarOpen) {
|
|
if (this._calendarNode.selectedDate) {
|
|
this._calendarNode.focusSelectedDate();
|
|
} else {
|
|
this._calendarNode.focusCentralDate();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {{ target: { selectedDate: Date }}} opts
|
|
*/
|
|
_onCalendarUserSelectedChanged({ target: { selectedDate } }) {
|
|
if (this._hideOnUserSelect) {
|
|
this._overlayCtrl.hide();
|
|
}
|
|
if (this._syncOnUserSelect) {
|
|
// Synchronize new selectedDate value to input
|
|
this.modelValue = selectedDate;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
return modelValue instanceof Date ? modelValue : undefined;
|
|
}
|
|
|
|
/**
|
|
* Validators contain the information to synchronize the input with
|
|
* the min, max and enabled dates of the calendar.
|
|
* @param {import('@lion/form-core').Validator[]} validators - errorValidators or warningValidators array
|
|
*/
|
|
__syncDisabledDates(validators) {
|
|
// On every validator change, synchronize disabled dates: this means
|
|
// we need to extract minDate, maxDate, minMaxDate and disabledDates validators
|
|
validators.forEach(v => {
|
|
const vctor = /** @type {typeof import('@lion/form-core').Validator} */ (v.constructor);
|
|
if (vctor.validatorName === 'MinDate') {
|
|
this.__calendarMinDate = v.param;
|
|
} else if (vctor.validatorName === 'MaxDate') {
|
|
this.__calendarMaxDate = v.param;
|
|
} else if (vctor.validatorName === 'MinMaxDate') {
|
|
this.__calendarMinDate = v.param.min;
|
|
this.__calendarMaxDate = v.param.max;
|
|
} else if (vctor.validatorName === 'IsDateDisabled') {
|
|
this.__calendarDisableDates = v.param;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @override Configures OverlayMixin
|
|
*/
|
|
get _overlayInvokerNode() {
|
|
return this._invokerNode;
|
|
}
|
|
|
|
/**
|
|
* @override Configures OverlayMixin
|
|
*/
|
|
get _overlayContentNode() {
|
|
if (this._cachedOverlayContentNode) {
|
|
return this._cachedOverlayContentNode;
|
|
}
|
|
this._cachedOverlayContentNode = /** @type {HTMLElement} */ (
|
|
/** @type {ShadowRoot} */ (this.shadowRoot).querySelector('.calendar__overlay-frame')
|
|
);
|
|
return this._cachedOverlayContentNode;
|
|
}
|
|
}
|