feat(input-datepicker): added input-datepicker component
Co-authored-by: Erik Kroes <erik.kroes@ing.com> Co-authored-by: Gerjan van Geest <gerjan.van.geest@ing.com> Co-authored-by: Mikhail Bashkirov <mikhail.bashkirov@ing.com>
This commit is contained in:
parent
144ebceb37
commit
9b110ca0f9
42 changed files with 1247 additions and 0 deletions
1
packages/input-datepicker/index.js
Normal file
1
packages/input-datepicker/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { LionInputDatepicker } from './src/LionInputDatepicker.js';
|
||||
3
packages/input-datepicker/lion-input-datepicker.js
Normal file
3
packages/input-datepicker/lion-input-datepicker.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { LionInputDatepicker } from './src/LionInputDatepicker.js';
|
||||
|
||||
customElements.define('lion-input-datepicker', LionInputDatepicker);
|
||||
49
packages/input-datepicker/package.json
Normal file
49
packages/input-datepicker/package.json
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"name": "@lion/input-datepicker",
|
||||
"version": "0.0.0",
|
||||
"description": "Provide a way for users to fill in a date via a calendar overlay",
|
||||
"author": "ing-bank",
|
||||
"homepage": "https://github.com/ing-bank/lion/",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ing-bank/lion.git",
|
||||
"directory": "packages/input-datepicker"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublishOnly": "../../scripts/npm-prepublish.js"
|
||||
},
|
||||
"keywords": [
|
||||
"lion",
|
||||
"web-components",
|
||||
"input-date",
|
||||
"input-datepicker",
|
||||
"calendar",
|
||||
"datepicker"
|
||||
],
|
||||
"main": "index.js",
|
||||
"module": "index.js",
|
||||
"files": [
|
||||
"src",
|
||||
"stories",
|
||||
"test",
|
||||
"*.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"@lion/core": "^0.1.3",
|
||||
"@lion/validate": "^0.1.3",
|
||||
"@lion/input-date": "^0.1.3",
|
||||
"@lion/overlays": "^0.1.3",
|
||||
"@lion/calendar": "^0.1.2",
|
||||
"@lion/localize": "^0.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@open-wc/demoing-storybook": "^0.2.0",
|
||||
"@open-wc/testing": "^0.11.1",
|
||||
"@lion/button": "^0.1.3",
|
||||
"@polymer/iron-test-helpers": "^3.0.1"
|
||||
}
|
||||
}
|
||||
149
packages/input-datepicker/src/LionCalendarOverlayFrame.js
Normal file
149
packages/input-datepicker/src/LionCalendarOverlayFrame.js
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
import { html, css, LitElement, DomHelpersMixin } from '@lion/core';
|
||||
import { LocalizeMixin } from '@lion/localize';
|
||||
|
||||
export class LionCalendarOverlayFrame extends LocalizeMixin(DomHelpersMixin(LitElement)) {
|
||||
static get styles() {
|
||||
return [
|
||||
css`
|
||||
:host {
|
||||
display: inline-block;
|
||||
background: white;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.calendar-overlay__header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.calendar-overlay__heading {
|
||||
padding: 16px 16px 8px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.calendar-overlay__heading > .calendar-overlay__close-button {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.calendar-overlay__close-button {
|
||||
min-width: 40px;
|
||||
min-height: 32px;
|
||||
border-width: 0;
|
||||
padding: 0;
|
||||
font-size: 24px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
static get localizeNamespaces() {
|
||||
return [
|
||||
{
|
||||
/* FIXME: This awful switch statement is used to make sure it works with polymer build.. */
|
||||
'lion-calendar-overlay-frame': locale => {
|
||||
switch (locale) {
|
||||
case 'bg-BG':
|
||||
return import('@lion/overlays/translations/bg-BG.js');
|
||||
case 'bg':
|
||||
return import('@lion/overlays/translations/bg.js');
|
||||
case 'cs-CZ':
|
||||
return import('@lion/overlays/translations/cs-CZ.js');
|
||||
case 'cs':
|
||||
return import('@lion/overlays/translations/cs.js');
|
||||
case 'de-DE':
|
||||
return import('@lion/overlays/translations/de-DE.js');
|
||||
case 'de':
|
||||
return import('@lion/overlays/translations/de.js');
|
||||
case 'en-AU':
|
||||
return import('@lion/overlays/translations/en-AU.js');
|
||||
case 'en-GB':
|
||||
return import('@lion/overlays/translations/en-GB.js');
|
||||
case 'en-US':
|
||||
return import('@lion/overlays/translations/en-US.js');
|
||||
case 'en':
|
||||
return import('@lion/overlays/translations/en.js');
|
||||
case 'es-ES':
|
||||
return import('@lion/overlays/translations/es-ES.js');
|
||||
case 'es':
|
||||
return import('@lion/overlays/translations/es.js');
|
||||
case 'fr-FR':
|
||||
return import('@lion/overlays/translations/fr-FR.js');
|
||||
case 'fr-BE':
|
||||
return import('@lion/overlays/translations/fr-BE.js');
|
||||
case 'fr':
|
||||
return import('@lion/overlays/translations/fr.js');
|
||||
case 'hu-HU':
|
||||
return import('@lion/overlays/translations/hu-HU.js');
|
||||
case 'hu':
|
||||
return import('@lion/overlays/translations/hu.js');
|
||||
case 'it-IT':
|
||||
return import('@lion/overlays/translations/it-IT.js');
|
||||
case 'it':
|
||||
return import('@lion/overlays/translations/it.js');
|
||||
case 'nl-BE':
|
||||
return import('@lion/overlays/translations/nl-BE.js');
|
||||
case 'nl-NL':
|
||||
return import('@lion/overlays/translations/nl-NL.js');
|
||||
case 'nl':
|
||||
return import('@lion/overlays/translations/nl.js');
|
||||
case 'pl-PL':
|
||||
return import('@lion/overlays/translations/pl-PL.js');
|
||||
case 'pl':
|
||||
return import('@lion/overlays/translations/pl.js');
|
||||
case 'ro-RO':
|
||||
return import('@lion/overlays/translations/ro-RO.js');
|
||||
case 'ro':
|
||||
return import('@lion/overlays/translations/ro.js');
|
||||
case 'ru-RU':
|
||||
return import('@lion/overlays/translations/ru-RU.js');
|
||||
case 'ru':
|
||||
return import('@lion/overlays/translations/ru.js');
|
||||
case 'sk-SK':
|
||||
return import('@lion/overlays/translations/sk-SK.js');
|
||||
case 'sk':
|
||||
return import('@lion/overlays/translations/sk.js');
|
||||
case 'uk-UA':
|
||||
return import('@lion/overlays/translations/uk-UA.js');
|
||||
case 'uk':
|
||||
return import('@lion/overlays/translations/uk.js');
|
||||
default:
|
||||
throw new Error(`Unknown locale: ${locale}`);
|
||||
}
|
||||
},
|
||||
},
|
||||
...super.localizeNamespaces,
|
||||
];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.__dispatchCloseEvent = this.__dispatchCloseEvent.bind(this);
|
||||
}
|
||||
|
||||
__dispatchCloseEvent() {
|
||||
// Designed to work in conjunction with ModalDialogController
|
||||
this.dispatchEvent(new CustomEvent('dialog-close'), { bubbles: true, composed: true });
|
||||
}
|
||||
|
||||
render() {
|
||||
// eslint-disable-line class-methods-use-this
|
||||
return html`
|
||||
<div class="calendar-overlay">
|
||||
<div class="calendar-overlay__header">
|
||||
<h1 class="calendar-overlay__heading">
|
||||
<slot name="heading"></slot>
|
||||
</h1>
|
||||
<button
|
||||
@click="${this.__dispatchCloseEvent}"
|
||||
id="close-button"
|
||||
title="${this.msgLit('lion-calendar-overlay-frame:close')}"
|
||||
aria-label="${this.msgLit('lion-calendar-overlay-frame:close')}"
|
||||
class="calendar-overlay__close-button"
|
||||
>
|
||||
<slot name="close-icon">×</slot>
|
||||
</button>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
365
packages/input-datepicker/src/LionInputDatepicker.js
Normal file
365
packages/input-datepicker/src/LionInputDatepicker.js
Normal file
|
|
@ -0,0 +1,365 @@
|
|||
import { html, render, ifDefined } from '@lion/core';
|
||||
import { LionInputDate } from '@lion/input-date';
|
||||
import { overlays, ModalDialogController } from '@lion/overlays';
|
||||
import { Unparseable, isValidatorApplied } from '@lion/validate';
|
||||
import '@lion/calendar/lion-calendar.js';
|
||||
import './lion-calendar-overlay-frame.js';
|
||||
|
||||
/**
|
||||
* @customElement
|
||||
* @extends {LionInputDate}
|
||||
*/
|
||||
export class LionInputDatepicker extends LionInputDate {
|
||||
static get properties() {
|
||||
return {
|
||||
...super.properties,
|
||||
/**
|
||||
* 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: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
/**
|
||||
* TODO: [delegation of disabled] move this to LionField (or FormControl) level
|
||||
*/
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
__calendarMinDate: {
|
||||
type: Date,
|
||||
},
|
||||
|
||||
__calendarMaxDate: {
|
||||
type: Date,
|
||||
},
|
||||
|
||||
__calendarDisableDates: {
|
||||
type: Function,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
get slots() {
|
||||
return {
|
||||
...super.slots,
|
||||
[this._calendarInvokerSlot]: () => this.__createPickerAndReturnInvokerNode(),
|
||||
};
|
||||
}
|
||||
|
||||
static get localizeNamespaces() {
|
||||
return [
|
||||
{
|
||||
/* FIXME: This awful switch statement is used to make sure it works with polymer build.. */
|
||||
'lion-input-datepicker': 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':
|
||||
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');
|
||||
default:
|
||||
throw new Error(`Unknown locale: ${locale}`);
|
||||
}
|
||||
},
|
||||
},
|
||||
...super.localizeNamespaces,
|
||||
];
|
||||
}
|
||||
|
||||
get _invokerElement() {
|
||||
return this.querySelector(`#${this.__invokerId}`);
|
||||
}
|
||||
|
||||
get _calendarOverlayElement() {
|
||||
return this._overlayCtrl._container.firstElementChild;
|
||||
}
|
||||
|
||||
get _calendarElement() {
|
||||
return this._calendarOverlayElement.querySelector('#calendar');
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
// Create a unique id for the invoker, since it is placed in light dom for a11y.
|
||||
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)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Problem: we need to create a getter for disabled that puts disabled attrs on the invoker
|
||||
* button.
|
||||
* The DelegateMixin creates getters and setters regardless of what's defined on the prototype,
|
||||
* thats why we need to move it out from parent delegations config, in order to make our own
|
||||
* getters and setters work.
|
||||
*
|
||||
* TODO: [delegation of disabled] fix this on a global level:
|
||||
* - LionField
|
||||
* - move all delegations of attrs and props to static get props for docs
|
||||
* - DelegateMixin needs to be refactored, so that it:
|
||||
* - gets config from static get properties
|
||||
* - hooks into _requestUpdate
|
||||
*/
|
||||
get delegations() {
|
||||
return {
|
||||
...super.delegations,
|
||||
properties: super.delegations.properties.filter(p => p !== 'disabled'),
|
||||
attributes: super.delegations.attributes.filter(p => p !== 'disabled'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: [delegation of disabled] move this to LionField (or FormControl) level
|
||||
*/
|
||||
_requestUpdate(name, oldValue) {
|
||||
super._requestUpdate(name, oldValue);
|
||||
if (name === 'disabled') {
|
||||
this.__delegateDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: [delegation of disabled] move this to LionField (or FormControl) level
|
||||
*/
|
||||
__delegateDisabled() {
|
||||
if (this.delegations.target()) {
|
||||
this.delegations.target().disabled = this.disabled;
|
||||
}
|
||||
if (this._invokerElement) {
|
||||
this._invokerElement.disabled = this.disabled;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: [delegation of disabled] move this to LionField (or FormControl) level
|
||||
*/
|
||||
firstUpdated(c) {
|
||||
super.firstUpdated(c);
|
||||
this.__delegateDisabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @param {Map} c - changed properties
|
||||
*/
|
||||
updated(c) {
|
||||
super.updated(c);
|
||||
|
||||
if (c.has('errorValidators') || c.has('warningValidators')) {
|
||||
const validators = [...(this.warningValidators || []), ...(this.errorValidators || [])];
|
||||
this.__syncDisabledDates(validators);
|
||||
}
|
||||
if (c.has('label')) {
|
||||
this.calendarHeading = this.calendarHeading || this.label;
|
||||
}
|
||||
}
|
||||
|
||||
_calendarOverlayTemplate() {
|
||||
return html`
|
||||
<lion-calendar-overlay-frame>
|
||||
<span slot="heading">${this.calendarHeading}</span>
|
||||
${this._calendarTemplate()}
|
||||
</lion-calendar-overlay-frame>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
id="calendar"
|
||||
.selectedDate="${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() {
|
||||
// TODO: aria-expanded should be toggled by Overlay system, to allow local overlays
|
||||
// (a.k.a. dropdowns) as well
|
||||
return html`
|
||||
<button
|
||||
@click="${this.__openCalendarOverlay}"
|
||||
id="${this.__invokerId}"
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded="false"
|
||||
aria-label="${this.msgLit('lion-input-datepicker:openDatepickerLabel')}"
|
||||
title="${this.msgLit('lion-input-datepicker:openDatepickerLabel')}"
|
||||
>
|
||||
📅
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
// Renders the invoker button + the calendar overlay invoked by this button
|
||||
__createPickerAndReturnInvokerNode() {
|
||||
const renderParent = document.createElement('div');
|
||||
render(this._invokerTemplate(), renderParent);
|
||||
const invokerNode = renderParent.firstElementChild;
|
||||
|
||||
// TODO: ModalDialogController should be replaced by a more flexible
|
||||
// overlay, allowing the overlay to switch on smaller screens, for instance from dropdown to
|
||||
// bottom sheet (working name for this controller: ResponsiveOverlayController)
|
||||
this._overlayCtrl = overlays.add(
|
||||
new ModalDialogController({
|
||||
contentTemplate: () => this._calendarOverlayTemplate(),
|
||||
elementToFocusAfterHide: invokerNode,
|
||||
}),
|
||||
);
|
||||
return invokerNode;
|
||||
}
|
||||
|
||||
async __openCalendarOverlay() {
|
||||
this._overlayCtrl.show();
|
||||
await this._calendarElement.updateComplete;
|
||||
this._onCalendarOverlayOpened();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle callback for subclassers
|
||||
*/
|
||||
_onCalendarOverlayOpened() {
|
||||
if (this._focusCentralDateOnCalendarOpen) {
|
||||
this._calendarElement.focusCentralDate();
|
||||
}
|
||||
}
|
||||
|
||||
_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'
|
||||
* @returns {Date|undefined} a 'guarded' modelValue
|
||||
*/
|
||||
static __getSyncDownValue(modelValue) {
|
||||
return modelValue instanceof Unparseable ? undefined : modelValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validators contain the information to synchronize the input with
|
||||
* the min, max and enabled dates of the calendar.
|
||||
* @param {Array} 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(([fn, param]) => {
|
||||
const d = new Date();
|
||||
|
||||
if (isValidatorApplied('minDate', fn, d)) {
|
||||
this.__calendarMinDate = param;
|
||||
} else if (isValidatorApplied('maxDate', fn, d)) {
|
||||
this.__calendarMaxDate = param;
|
||||
} else if (isValidatorApplied('minMaxDate', fn, { min: d, max: d })) {
|
||||
this.__calendarMinDate = param.min;
|
||||
this.__calendarMaxDate = param.max;
|
||||
} else if (isValidatorApplied('isDateDisabled', fn, () => true)) {
|
||||
this.__calendarDisableDates = param;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import { LionCalendarOverlayFrame } from './LionCalendarOverlayFrame.js';
|
||||
|
||||
customElements.define('lion-calendar-overlay-frame', LionCalendarOverlayFrame);
|
||||
54
packages/input-datepicker/stories/index.stories.js
Normal file
54
packages/input-datepicker/stories/index.stories.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { storiesOf, html } from '@open-wc/demoing-storybook';
|
||||
import { isDateDisabledValidator, minMaxDateValidator } from '@lion/validate';
|
||||
import '../lion-input-datepicker.js';
|
||||
|
||||
storiesOf('Forms|Input Datepicker', module)
|
||||
.add(
|
||||
'Default',
|
||||
() => html`
|
||||
<lion-input-datepicker label="Date" .modelValue=${new Date('2017/06/15')}>
|
||||
</lion-input-datepicker>
|
||||
`,
|
||||
)
|
||||
.add(
|
||||
'minMaxDateValidator',
|
||||
() => html`
|
||||
<lion-input-datepicker
|
||||
label="MinMaxDate"
|
||||
help-text="Enter a date between '2018/05/24' and '2018/06/24'"
|
||||
.modelValue=${new Date('2018/05/30')}
|
||||
.errorValidators=${[
|
||||
minMaxDateValidator({ min: new Date('2018/05/24'), max: new Date('2018/06/24') }),
|
||||
]}
|
||||
>
|
||||
</lion-input-datepicker>
|
||||
`,
|
||||
)
|
||||
.add(
|
||||
'disabledDatesValidator',
|
||||
() => html`
|
||||
<lion-input-datepicker
|
||||
label="disabledDates"
|
||||
help-text="You're not allowed to choose the 15th"
|
||||
.errorValidators=${[isDateDisabledValidator(d => d.getDate() === 15)]}
|
||||
>
|
||||
</lion-input-datepicker>
|
||||
`,
|
||||
)
|
||||
.add(
|
||||
'With calendar-heading',
|
||||
() => html`
|
||||
<lion-input-datepicker
|
||||
label="Date"
|
||||
.calendarHeading="${'Custom heading'}"
|
||||
.modelValue=${new Date()}
|
||||
>
|
||||
</lion-input-datepicker>
|
||||
`,
|
||||
)
|
||||
.add(
|
||||
'Disabled',
|
||||
() => html`
|
||||
<lion-input-datepicker disabled></lion-input-datepicker>
|
||||
`,
|
||||
);
|
||||
425
packages/input-datepicker/test/lion-input-datepicker.test.js
Normal file
425
packages/input-datepicker/test/lion-input-datepicker.test.js
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
import { expect, fixture, aTimeout, defineCE } from '@open-wc/testing';
|
||||
import { localizeTearDown } from '@lion/localize/test-helpers.js';
|
||||
import { html, LitElement } from '@lion/core';
|
||||
import {
|
||||
maxDateValidator,
|
||||
minDateValidator,
|
||||
minMaxDateValidator,
|
||||
isDateDisabledValidator,
|
||||
} from '@lion/validate';
|
||||
import { keyCodes } from '@lion/overlays/src/utils/key-codes.js';
|
||||
import { keyUpOn } from '@polymer/iron-test-helpers/mock-interactions.js';
|
||||
import { LionCalendar } from '@lion/calendar';
|
||||
import { isSameDate } from '@lion/calendar/src/utils/isSameDate.js';
|
||||
import { DatepickerInputObject } from './test-utils.js';
|
||||
import { LionInputDatepicker } from '../src/LionInputDatepicker.js';
|
||||
import '../lion-input-datepicker.js';
|
||||
|
||||
describe('<lion-input-datepicker>', () => {
|
||||
beforeEach(() => {
|
||||
localizeTearDown();
|
||||
});
|
||||
|
||||
describe('Calendar Overlay', () => {
|
||||
it('implements calendar-overlay Style component', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker></lion-input-datepicker>
|
||||
`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
|
||||
expect(elObj.overlayEl.shadowRoot.querySelector('.calendar-overlay')).not.to.equal(null);
|
||||
expect(elObj.overlayEl.shadowRoot.querySelector('.calendar-overlay__header')).not.to.equal(
|
||||
null,
|
||||
);
|
||||
expect(elObj.overlayEl.shadowRoot.querySelector('.calendar-overlay__heading')).not.to.equal(
|
||||
null,
|
||||
);
|
||||
expect(
|
||||
elObj.overlayEl.shadowRoot.querySelector('.calendar-overlay__close-button'),
|
||||
).not.to.equal(null);
|
||||
});
|
||||
|
||||
it.skip('activates full screen mode on mobile screens', async () => {
|
||||
// TODO: should this be part of globalOverlayController as option?
|
||||
});
|
||||
|
||||
it('has a close button, with a tooltip "Close"', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker></lion-input-datepicker>
|
||||
`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
// Since tooltip not ready, use title which can be progressively enhanced in extension layers.
|
||||
expect(elObj.overlayCloseButtonEl.getAttribute('title')).to.equal('Close');
|
||||
expect(elObj.overlayCloseButtonEl.getAttribute('aria-label')).to.equal('Close');
|
||||
});
|
||||
|
||||
it('has a default title based on input label', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker
|
||||
.label="${'Pick your date'}"
|
||||
.modelValue="${new Date('2020-02-15')}"
|
||||
></lion-input-datepicker>
|
||||
`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
expect(
|
||||
elObj.overlayHeadingEl.querySelector('slot[name="heading"]').assignedNodes()[0],
|
||||
).lightDom.to.equal('Pick your date');
|
||||
});
|
||||
|
||||
it('can have a custom heading', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker
|
||||
label="Pick your date"
|
||||
calendar-heading="foo"
|
||||
></lion-input-datepicker>
|
||||
`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
expect(
|
||||
elObj.overlayHeadingEl.querySelector('slot[name="heading"]').assignedNodes()[0],
|
||||
).lightDom.to.equal('foo');
|
||||
});
|
||||
|
||||
// TODO: fix the Overlay system, so that the backdrop/body cannot be focused
|
||||
it('closes the calendar on [esc] key', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker></lion-input-datepicker>
|
||||
`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
expect(elObj.overlayController.isShown).to.equal(true);
|
||||
// Mimic user input: should fire the 'selected-date-changed' event
|
||||
// Make sure focus is inside the calendar/overlay
|
||||
keyUpOn(elObj.calendarEl, keyCodes.escape);
|
||||
expect(elObj.overlayController.isShown).to.equal(false);
|
||||
});
|
||||
|
||||
/**
|
||||
* Not in scope:
|
||||
* - centralDate can be overridden
|
||||
*/
|
||||
});
|
||||
|
||||
describe('Calendar Invoker', () => {
|
||||
it('adds invoker button that toggles the overlay on click in suffix slot ', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker></lion-input-datepicker>
|
||||
`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
expect(elObj.invokerEl).not.to.equal(null);
|
||||
expect(elObj.overlayController.isShown).to.be.false;
|
||||
await elObj.openCalendar();
|
||||
expect(elObj.overlayController.isShown).to.equal(true);
|
||||
});
|
||||
|
||||
// Relies on delegation of disabled property to invoker.
|
||||
// TODO: consider making this (delegation to interactive child nodes) generic functionality
|
||||
// inside LionField/FormControl. Or, for maximum flexibility, add a config attr
|
||||
// to the invoker node like 'data-disabled-is-delegated'
|
||||
it('delegates disabled state of host input', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker disabled></lion-input-datepicker>
|
||||
`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
expect(elObj.overlayController.isShown).to.equal(false);
|
||||
await elObj.openCalendar();
|
||||
expect(elObj.overlayController.isShown).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Input - calendar synchronization', () => {
|
||||
it('syncs modelValue with lion-calendar', async () => {
|
||||
const myDate = new Date('2019/06/15');
|
||||
const myOtherDate = new Date('2019/06/28');
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker .modelValue="${myDate}"></lion-input-datepicker>
|
||||
`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
expect(elObj.calendarEl.selectedDate).to.equal(myDate);
|
||||
await elObj.selectMonthDay(myOtherDate.getDate());
|
||||
expect(isSameDate(el.modelValue, myOtherDate)).to.be.true;
|
||||
});
|
||||
|
||||
it('closes the calendar overlay on "user-selected-date-changed"', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker></lion-input-datepicker>
|
||||
`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
// Make sure the calendar overlay is opened
|
||||
await elObj.openCalendar();
|
||||
expect(elObj.overlayController.isShown).to.equal(true);
|
||||
// Mimic user input: should fire the 'user-selected-date-changed' event
|
||||
await elObj.selectMonthDay(12);
|
||||
expect(elObj.overlayController.isShown).to.equal(false);
|
||||
});
|
||||
|
||||
it('focuses interactable date on opening of calendar', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker></lion-input-datepicker>
|
||||
`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
await aTimeout();
|
||||
expect(elObj.calendarObj.focusedDayObj.el).not.to.equal(null);
|
||||
});
|
||||
|
||||
describe('Validators', () => {
|
||||
/**
|
||||
* Validators are the Application Developer facing API in <lion-input-datepicker>:
|
||||
* - setting restrictions on min/max/disallowed dates will be done via validators
|
||||
* - all validators will be translated under the hood to enabledDates and passed to
|
||||
* lion-calendar
|
||||
*/
|
||||
it('converts isDateDisabledValidator to "disableDates" property', async () => {
|
||||
const no15th = d => d.getDate() !== 15;
|
||||
const no16th = d => d.getDate() !== 16;
|
||||
const no15thOr16th = d => no15th(d) && no16th(d);
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker .errorValidators="${[isDateDisabledValidator(no15thOr16th)]}">
|
||||
</lion-input-datepicker>
|
||||
`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
|
||||
expect(elObj.calendarEl.disableDates).to.equal(no15thOr16th);
|
||||
});
|
||||
|
||||
it('converts minDateValidator to "minDate" property', async () => {
|
||||
const myMinDate = new Date('2019/06/15');
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker .errorValidators=${[minDateValidator(myMinDate)]}>
|
||||
</lion-input-date>`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
|
||||
expect(elObj.calendarEl.minDate).to.equal(myMinDate);
|
||||
});
|
||||
|
||||
it('converts maxDateValidator to "maxDate" property', async () => {
|
||||
const myMaxDate = new Date('2030/06/15');
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker .errorValidators=${[maxDateValidator(myMaxDate)]}>
|
||||
</lion-input-datepicker>
|
||||
`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
|
||||
expect(elObj.calendarEl.maxDate).to.equal(myMaxDate);
|
||||
});
|
||||
|
||||
it('converts minMaxDateValidator to "minDate" and "maxDate" property', async () => {
|
||||
const myMinDate = new Date('2019/06/15');
|
||||
const myMaxDate = new Date('2030/06/15');
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker
|
||||
.errorValidators=${[minMaxDateValidator({ min: myMinDate, max: myMaxDate })]}
|
||||
>
|
||||
</lion-input-datepicker>
|
||||
`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
|
||||
expect(elObj.calendarEl.minDate).to.equal(myMinDate);
|
||||
expect(elObj.calendarEl.maxDate).to.equal(myMaxDate);
|
||||
});
|
||||
|
||||
/**
|
||||
* Not in scope:
|
||||
* - min/max attr (like platform has): could be added in future if observers needed
|
||||
*/
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('has a heading of level 1', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker calendar-heading="foo"></lion-input-datepicker>
|
||||
`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
|
||||
const hNode = elObj.overlayHeadingEl;
|
||||
const headingIsLevel1 =
|
||||
hNode.tagName === 'H1' ||
|
||||
(hNode.getAttribute('role') === 'heading' && hNode.getAttribute('aria-level') === '1');
|
||||
expect(headingIsLevel1).to.be.true;
|
||||
});
|
||||
|
||||
it('adds accessible label to invoker button', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker></lion-input-datepicker>
|
||||
`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
|
||||
expect(elObj.invokerEl.getAttribute('title')).to.equal('Open date picker');
|
||||
expect(elObj.invokerEl.getAttribute('aria-label')).to.equal('Open date picker');
|
||||
});
|
||||
|
||||
// TODO: move this functionality to GlobalOverlay
|
||||
it('adds aria-haspopup="dialog" and aria-expanded="true" to invoker button', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-input-datepicker></lion-input-datepicker>
|
||||
`);
|
||||
const elObj = new DatepickerInputObject(el);
|
||||
|
||||
expect(elObj.invokerEl.getAttribute('aria-haspopup')).to.equal('dialog');
|
||||
expect(elObj.invokerEl.getAttribute('aria-expanded')).to.equal('false');
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('Subclassers', () => {
|
||||
describe('Providing a custom invoker', () => {
|
||||
it('can override the invoker template', async () => {
|
||||
const myTag = defineCE(
|
||||
class extends LionInputDatepicker {
|
||||
/** @override */
|
||||
_invokerTemplate() {
|
||||
return html`
|
||||
<my-button>Pick my date</my-button>
|
||||
`;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const myEl = await fixture(`<${myTag}></${myTag}>`);
|
||||
const myElObj = new DatepickerInputObject(myEl);
|
||||
expect(myElObj.invokerEl.tagName.toLowerCase()).to.equal('my-button');
|
||||
|
||||
// All other tests will still pass. Small checkup:
|
||||
expect(myElObj.invokerEl.getAttribute('title')).to.equal('Open date picker');
|
||||
expect(myElObj.invokerEl.getAttribute('aria-label')).to.equal('Open date picker');
|
||||
expect(myElObj.invokerEl.getAttribute('aria-expanded')).to.equal('false');
|
||||
expect(myElObj.invokerEl.getAttribute('aria-haspopup')).to.equal('dialog');
|
||||
expect(myElObj.invokerEl.getAttribute('slot')).to.equal('suffix');
|
||||
expect(myElObj.invokerEl.getAttribute('id')).to.equal(myEl.__invokerId);
|
||||
await myElObj.openCalendar();
|
||||
expect(myElObj.overlayController.isShown).to.equal(true);
|
||||
});
|
||||
|
||||
it('can allocate the picker in a different slot supported by LionField', async () => {
|
||||
/**
|
||||
* It's important that this api is used instead of Subclassers providing a slot.
|
||||
* When the input-datepicker knows where the calendar invoker is, it can attach
|
||||
* the right logic, localization and accessibility functionality.
|
||||
*/
|
||||
const myTag = defineCE(
|
||||
class extends LionInputDatepicker {
|
||||
constructor() {
|
||||
super();
|
||||
this._calendarInvokerSlot = 'prefix';
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const myEl = await fixture(`<${myTag}></${myTag}>`);
|
||||
const myElObj = new DatepickerInputObject(myEl);
|
||||
expect(myElObj.invokerEl.getAttribute('slot')).to.equal('prefix');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Providing a custom calendar', () => {
|
||||
it('can override the calendar template', async () => {
|
||||
customElements.define(
|
||||
'my-calendar',
|
||||
class extends LionCalendar {
|
||||
constructor() {
|
||||
super();
|
||||
// Change some defaults
|
||||
this.firstDayOfWeek = 1; // Start on Mondays instead of Sundays
|
||||
this.weekdayHeaderNotation = 'narrow'; // 'T' instead of 'Thu'
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const myTag = defineCE(
|
||||
class extends LionInputDatepicker {
|
||||
_calendarTemplate() {
|
||||
return html`
|
||||
<my-calendar id="calendar"></my-calendar>
|
||||
`;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const myEl = await fixture(`<${myTag}></${myTag}>`);
|
||||
const myElObj = new DatepickerInputObject(myEl);
|
||||
|
||||
// All other tests will still pass. Small checkup:
|
||||
await myElObj.openCalendar();
|
||||
expect(myElObj.calendarEl.tagName.toLowerCase()).to.equal('my-calendar');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Providing a custom overlay', () => {
|
||||
it('can override the overlay template', async () => {
|
||||
// Keep in mind there is no logic inside this overlay frame; it only handles visuals.
|
||||
// All interaction should be delegated to parent, which interacts with the calendar
|
||||
// component
|
||||
customElements.define(
|
||||
'my-calendar-overlay-frame',
|
||||
class extends LitElement {
|
||||
render() {
|
||||
// eslint-disable-line class-methods-use-this
|
||||
return html`
|
||||
<div class="c-calendar-overlay">
|
||||
<slot></slot>
|
||||
<div class="c-calendar-overlay__footer">
|
||||
<button id="cancel-button" class="c-calendar-overlay__cancel-button">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let myOverlayOpenedCbHandled = false;
|
||||
let myUserSelectedChangedCbHandled = false;
|
||||
|
||||
const myTag = defineCE(
|
||||
class extends LionInputDatepicker {
|
||||
/** @override */
|
||||
_calendarOverlayTemplate() {
|
||||
return html`
|
||||
<my-calendar-overlay-frame id="calendar-overlay">
|
||||
<span slot="heading">${this.calendarHeading}</span>
|
||||
${this._calendarTemplateConfig(this._calendarTemplate())}
|
||||
</my-calendar-overlay-frame>
|
||||
`;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_onCalendarOverlayOpened(...args) {
|
||||
super._onCalendarOverlayOpened(...args);
|
||||
myOverlayOpenedCbHandled = true;
|
||||
}
|
||||
|
||||
/** @override */
|
||||
_onCalendarUserSelectedChanged(...args) {
|
||||
super._onCalendarUserSelectedChanged(...args);
|
||||
myUserSelectedChangedCbHandled = true;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const myEl = await fixture(`<${myTag}></${myTag}>`);
|
||||
const myElObj = new DatepickerInputObject(myEl);
|
||||
|
||||
// All other tests will still pass. Small checkup:
|
||||
await myElObj.openCalendar();
|
||||
expect(myElObj.overlayEl.tagName.toLowerCase()).to.equal('my-calendar-overlay-frame');
|
||||
expect(myOverlayOpenedCbHandled).to.be.true;
|
||||
await myElObj.selectMonthDay(1);
|
||||
expect(myUserSelectedChangedCbHandled).to.be.true;
|
||||
});
|
||||
|
||||
it.skip('can configure the overlay presentation based on media query switch', async () => {});
|
||||
});
|
||||
});
|
||||
});
|
||||
65
packages/input-datepicker/test/test-utils.js
Normal file
65
packages/input-datepicker/test/test-utils.js
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { CalendarObject } from '@lion/calendar/test/test-utils.js';
|
||||
|
||||
// TODO: refactor CalendarObject to this approach (only methods when arguments are needed)
|
||||
export class DatepickerInputObject {
|
||||
constructor(el) {
|
||||
this.el = el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods mimicing User Interaction
|
||||
*/
|
||||
|
||||
async openCalendar() {
|
||||
// Make sure the calendar is opened, not closed/toggled;
|
||||
this.overlayController.hide();
|
||||
this.invokerEl.click();
|
||||
return this.calendarEl ? this.calendarEl.updateComplete : false;
|
||||
}
|
||||
|
||||
async selectMonthDay(day) {
|
||||
this.overlayController.show();
|
||||
await this.calendarEl.updateComplete;
|
||||
this.calendarObj.getDayEl(day).click();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Node references
|
||||
*/
|
||||
|
||||
get invokerEl() {
|
||||
return this.el._invokerElement;
|
||||
}
|
||||
|
||||
get overlayEl() {
|
||||
return this.el._overlayCtrl._container && this.el._overlayCtrl._container.firstElementChild;
|
||||
}
|
||||
|
||||
get overlayHeadingEl() {
|
||||
return this.overlayEl && this.overlayEl.shadowRoot.querySelector('.calendar-overlay__heading');
|
||||
}
|
||||
|
||||
get overlayCloseButtonEl() {
|
||||
return this.calendarEl && this.overlayEl.shadowRoot.querySelector('#close-button');
|
||||
}
|
||||
|
||||
get calendarEl() {
|
||||
return this.overlayEl && this.overlayEl.querySelector('#calendar');
|
||||
}
|
||||
|
||||
/**
|
||||
* @property {CalendarObject}
|
||||
*/
|
||||
get calendarObj() {
|
||||
return this.calendarEl && new CalendarObject(this.calendarEl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Object references
|
||||
*/
|
||||
|
||||
get overlayController() {
|
||||
return this.el._overlayCtrl;
|
||||
}
|
||||
}
|
||||
5
packages/input-datepicker/translations/bg-BG.js
Normal file
5
packages/input-datepicker/translations/bg-BG.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import bg from './bg.js';
|
||||
|
||||
export default {
|
||||
...bg,
|
||||
};
|
||||
3
packages/input-datepicker/translations/bg.js
Normal file
3
packages/input-datepicker/translations/bg.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
openDatepickerLabel: 'Избор на отворена дата',
|
||||
};
|
||||
5
packages/input-datepicker/translations/cs-CZ.js
Normal file
5
packages/input-datepicker/translations/cs-CZ.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import cs from './cs.js';
|
||||
|
||||
export default {
|
||||
...cs,
|
||||
};
|
||||
3
packages/input-datepicker/translations/cs.js
Normal file
3
packages/input-datepicker/translations/cs.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
openDatepickerLabel: 'Otevřete pro výběr data',
|
||||
};
|
||||
5
packages/input-datepicker/translations/de-DE.js
Normal file
5
packages/input-datepicker/translations/de-DE.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import de from './de.js';
|
||||
|
||||
export default {
|
||||
...de,
|
||||
};
|
||||
3
packages/input-datepicker/translations/de.js
Normal file
3
packages/input-datepicker/translations/de.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
openDatepickerLabel: 'Datumswähler öffnen',
|
||||
};
|
||||
5
packages/input-datepicker/translations/en-AU.js
Normal file
5
packages/input-datepicker/translations/en-AU.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import en from './en.js';
|
||||
|
||||
export default {
|
||||
...en,
|
||||
};
|
||||
5
packages/input-datepicker/translations/en-GB.js
Normal file
5
packages/input-datepicker/translations/en-GB.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import en from './en.js';
|
||||
|
||||
export default {
|
||||
...en,
|
||||
};
|
||||
5
packages/input-datepicker/translations/en-US.js
Normal file
5
packages/input-datepicker/translations/en-US.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import en from './en.js';
|
||||
|
||||
export default {
|
||||
...en,
|
||||
};
|
||||
3
packages/input-datepicker/translations/en.js
Normal file
3
packages/input-datepicker/translations/en.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
openDatepickerLabel: 'Open date picker',
|
||||
};
|
||||
5
packages/input-datepicker/translations/es-ES.js
Normal file
5
packages/input-datepicker/translations/es-ES.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import es from './es.js';
|
||||
|
||||
export default {
|
||||
...es,
|
||||
};
|
||||
3
packages/input-datepicker/translations/es.js
Normal file
3
packages/input-datepicker/translations/es.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
openDatepickerLabel: 'Selector de fecha abierta',
|
||||
};
|
||||
5
packages/input-datepicker/translations/fr-BE.js
Normal file
5
packages/input-datepicker/translations/fr-BE.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import fr from './fr.js';
|
||||
|
||||
export default {
|
||||
...fr,
|
||||
};
|
||||
5
packages/input-datepicker/translations/fr-FR.js
Normal file
5
packages/input-datepicker/translations/fr-FR.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import fr from './fr.js';
|
||||
|
||||
export default {
|
||||
...fr,
|
||||
};
|
||||
3
packages/input-datepicker/translations/fr.js
Normal file
3
packages/input-datepicker/translations/fr.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
openDatepickerLabel: 'Ouvrir le sélecteur de dates',
|
||||
};
|
||||
5
packages/input-datepicker/translations/hu-HU.js
Normal file
5
packages/input-datepicker/translations/hu-HU.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import hu from './hu.js';
|
||||
|
||||
export default {
|
||||
...hu,
|
||||
};
|
||||
3
packages/input-datepicker/translations/hu.js
Normal file
3
packages/input-datepicker/translations/hu.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
openDatepickerLabel: 'Dátumválasztó megnyitása',
|
||||
};
|
||||
5
packages/input-datepicker/translations/it-IT.js
Normal file
5
packages/input-datepicker/translations/it-IT.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import it from './it.js';
|
||||
|
||||
export default {
|
||||
...it,
|
||||
};
|
||||
3
packages/input-datepicker/translations/it.js
Normal file
3
packages/input-datepicker/translations/it.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
openDatepickerLabel: 'Raccoglitore di data aperta',
|
||||
};
|
||||
5
packages/input-datepicker/translations/nl-BE.js
Normal file
5
packages/input-datepicker/translations/nl-BE.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import nl from './nl.js';
|
||||
|
||||
export default {
|
||||
...nl,
|
||||
};
|
||||
5
packages/input-datepicker/translations/nl-NL.js
Normal file
5
packages/input-datepicker/translations/nl-NL.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import nl from './nl.js';
|
||||
|
||||
export default {
|
||||
...nl,
|
||||
};
|
||||
3
packages/input-datepicker/translations/nl.js
Normal file
3
packages/input-datepicker/translations/nl.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
openDatepickerLabel: 'Open kalender',
|
||||
};
|
||||
5
packages/input-datepicker/translations/pl-PL.js
Normal file
5
packages/input-datepicker/translations/pl-PL.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import pl from './pl.js';
|
||||
|
||||
export default {
|
||||
...pl,
|
||||
};
|
||||
3
packages/input-datepicker/translations/pl.js
Normal file
3
packages/input-datepicker/translations/pl.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
openDatepickerLabel: 'Otwórz pole daty',
|
||||
};
|
||||
5
packages/input-datepicker/translations/ro-RO.js
Normal file
5
packages/input-datepicker/translations/ro-RO.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import ro from './ro.js';
|
||||
|
||||
export default {
|
||||
...ro,
|
||||
};
|
||||
3
packages/input-datepicker/translations/ro.js
Normal file
3
packages/input-datepicker/translations/ro.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
openDatepickerLabel: 'Deschidere selector dată',
|
||||
};
|
||||
5
packages/input-datepicker/translations/ru-RU.js
Normal file
5
packages/input-datepicker/translations/ru-RU.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import ru from './ru.js';
|
||||
|
||||
export default {
|
||||
...ru,
|
||||
};
|
||||
3
packages/input-datepicker/translations/ru.js
Normal file
3
packages/input-datepicker/translations/ru.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
openDatepickerLabel: 'Открыть модуль выбора даты',
|
||||
};
|
||||
5
packages/input-datepicker/translations/sk-SK.js
Normal file
5
packages/input-datepicker/translations/sk-SK.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import sk from './sk.js';
|
||||
|
||||
export default {
|
||||
...sk,
|
||||
};
|
||||
3
packages/input-datepicker/translations/sk.js
Normal file
3
packages/input-datepicker/translations/sk.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
openDatepickerLabel: 'Otvoriť nástroj na výber dátumu',
|
||||
};
|
||||
5
packages/input-datepicker/translations/uk-UA.js
Normal file
5
packages/input-datepicker/translations/uk-UA.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import uk from './uk.js';
|
||||
|
||||
export default {
|
||||
...uk,
|
||||
};
|
||||
3
packages/input-datepicker/translations/uk.js
Normal file
3
packages/input-datepicker/translations/uk.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
openDatepickerLabel: 'Відкрити модуль вибору дати',
|
||||
};
|
||||
|
|
@ -6,6 +6,7 @@ import '../packages/input/stories/localize.stories.js';
|
|||
import '../packages/textarea/stories/index.stories.js';
|
||||
import '../packages/input-amount/stories/index.stories.js';
|
||||
import '../packages/input-date/stories/index.stories.js';
|
||||
import '../packages/input-datepicker/stories/index.stories.js';
|
||||
import '../packages/input-email/stories/index.stories.js';
|
||||
import '../packages/input-iban/stories/index.stories.js';
|
||||
import '../packages/select/stories/index.stories.js';
|
||||
|
|
|
|||
Loading…
Reference in a new issue