@limetech/lime-elements
Version:
466 lines (465 loc) • 15 kB
JavaScript
import { h, } from '@stencil/core';
import { createRandomString } from '../../util/random-string';
import { isAndroidDevice, isIOSDevice } from '../../util/device';
import { DateFormatter } from './date-formatter';
import { MDCTextField } from '@material/textfield';
// tslint:disable:no-duplicate-string
const nativeTypeForConsumerType = {
date: 'date',
time: 'time',
// Mobile Safari feature detects as capable of input type `week`,
// but it just displays a non-interactive input
// TODO(ads): remove this when support is decent on iOS!
week: isIOSDevice() ? 'date' : 'week',
month: 'month',
quarter: 'date',
year: 'date',
datetime: 'datetime-local',
default: 'datetime-local',
};
const nativeFormatForType = {
date: 'Y-MM-DD',
time: 'HH:mm',
week: 'GGGG-[W]WW',
month: 'Y-MM',
'datetime-local': 'Y-MM-DD[T]HH:mm',
};
// tslint:enable:no-duplicate-string
/**
* @exampleComponent limel-example-date-picker-datetime
* @exampleComponent limel-example-date-picker-date
* @exampleComponent limel-example-date-picker-time
* @exampleComponent limel-example-date-picker-week
* @exampleComponent limel-example-date-picker-month
* @exampleComponent limel-example-date-picker-quarter
* @exampleComponent limel-example-date-picker-year
* @exampleComponent limel-example-date-picker-formatted
* @exampleComponent limel-example-date-picker-programmatic-change
* @exampleComponent limel-example-date-picker-composite
* @exampleComponent limel-example-date-picker-custom-formatter
*/
export class DatePicker {
constructor() {
this.portalId = `date-picker-calendar-${createRandomString()}`;
this.documentClickListener = (event) => {
if (event.composedPath().includes(this.textField)) {
return;
}
const element = document.querySelector(`#${this.portalId}`);
if (!element.contains(event.target)) {
this.hideCalendar();
}
};
this.formatValue = (value) => this.dateFormatter.formatDate(value, this.internalFormat);
this.disabled = false;
this.readonly = false;
this.invalid = false;
this.label = undefined;
this.placeholder = undefined;
this.helperText = undefined;
this.required = false;
this.value = undefined;
this.type = 'datetime';
this.format = undefined;
this.language = 'en';
this.formatter = undefined;
this.internalFormat = undefined;
this.showPortal = false;
this.handleCalendarChange = this.handleCalendarChange.bind(this);
this.handleInputElementChange =
this.handleInputElementChange.bind(this);
this.showCalendar = this.showCalendar.bind(this);
this.dateFormatter = new DateFormatter(this.language);
this.clearValue = this.clearValue.bind(this);
this.hideCalendar = this.hideCalendar.bind(this);
this.onInputClick = this.onInputClick.bind(this);
this.nativeChangeHandler = this.nativeChangeHandler.bind(this);
this.preventBlurFromCalendarContainer =
this.preventBlurFromCalendarContainer.bind(this);
}
componentWillLoad() {
this.useNative = !this.readonly && (isIOSDevice() || isAndroidDevice());
this.updateInternalFormatAndType();
}
componentWillUpdate() {
this.updateInternalFormatAndType();
}
disconnectedCallback() {
this.hideCalendar();
}
render() {
const inputProps = {
onAction: this.clearValue,
};
if (this.value && !this.readonly) {
inputProps.trailingIcon = 'clear_symbol';
}
if (this.useNative) {
return (h("limel-input-field", { disabled: this.disabled, readonly: this.readonly, invalid: this.invalid, label: this.label, helperText: this.helperText, required: this.required, value: this.formatValue(this.value), type: this.nativeType, onChange: this.nativeChangeHandler }));
}
const dropdownZIndex = getComputedStyle(this.host).getPropertyValue('--dropdown-z-index');
const formatter = this.formatter || this.formatValue;
return [
h("limel-input-field", Object.assign({ disabled: this.disabled, readonly: this.readonly, invalid: this.invalid, label: this.label, placeholder: this.placeholder, helperText: this.helperText, required: this.required, value: this.value ? formatter(this.value) : '', onFocus: this.showCalendar, onBlur: this.hideCalendar, onClick: this.onInputClick, onChange: this.handleInputElementChange, ref: (el) => (this.textField = el) }, inputProps)),
h("limel-portal", { containerId: this.portalId, visible: this.showPortal, containerStyle: { 'z-index': dropdownZIndex } }, h("limel-flatpickr-adapter", { format: this.internalFormat, language: this.language, type: this.type, value: this.value, ref: (el) => (this.datePickerCalendar = el), isOpen: this.showPortal, formatter: formatter, onChange: this.handleCalendarChange })),
];
}
updateInternalFormatAndType() {
this.nativeType = nativeTypeForConsumerType[this.type || 'default'];
this.nativeFormat = nativeFormatForType[this.nativeType];
if (this.useNative) {
this.internalFormat = this.nativeFormat;
}
else if (this.formatter || this.format) {
this.internalFormat = this.format;
}
else {
this.internalFormat = this.dateFormatter.getDateFormat(this.type);
}
}
nativeChangeHandler(event) {
event.stopPropagation();
const date = this.dateFormatter.parseDate(event.detail, this.internalFormat);
this.change.emit(date);
}
showCalendar(event) {
this.showPortal = true;
const inputElement = this.textField.shadowRoot.querySelector('input');
setTimeout(() => {
this.datePickerCalendar.inputElement = inputElement;
});
event.stopPropagation();
document.addEventListener('mousedown', this.documentClickListener, {
passive: true,
});
document.addEventListener('blur', this.preventBlurFromCalendarContainer, {
capture: true,
});
}
preventBlurFromCalendarContainer(event) {
// We don't want the input element to lose focus when we pick
// a date in the calendar container.
// This is also required in order to not close the non
// automatically closing pickers (type datetime and time)
// when you pick a value.
if (event.relatedTarget === this.datePickerCalendar) {
event.stopPropagation();
}
}
hideCalendar() {
setTimeout(() => {
this.showPortal = false;
});
document.removeEventListener('mousedown', this.documentClickListener);
document.removeEventListener('blur', this.preventBlurFromCalendarContainer);
if (!this.pickerIsAutoClosing()) {
this.fixFlatpickrFocusBug();
}
}
fixFlatpickrFocusBug() {
// Flatpickr removes the focus from the input field
// but the 'visual focus' is still there
const mdcTextField = new MDCTextField(this.textField.shadowRoot.querySelector('.mdc-text-field'));
mdcTextField.getDefaultFoundation().deactivateFocus();
mdcTextField.valid = !this.invalid;
}
handleCalendarChange(event) {
const date = event.detail;
event.stopPropagation();
if (this.pickerIsAutoClosing()) {
this.hideCalendar();
}
this.change.emit(date);
}
onInputClick(event) {
if (this.disabled || this.readonly) {
return;
}
if (this.showPortal) {
return;
}
this.showCalendar(event);
}
handleInputElementChange(event) {
if (event.detail === '') {
this.clearValue();
}
event.stopPropagation();
}
pickerIsAutoClosing() {
return this.type !== 'datetime' && this.type !== 'time';
}
clearValue() {
this.change.emit(null);
}
static get is() { return "limel-date-picker"; }
static get encapsulation() { return "shadow"; }
static get originalStyleUrls() {
return {
"$": ["date-picker.scss"]
};
}
static get styleUrls() {
return {
"$": ["date-picker.css"]
};
}
static get properties() {
return {
"disabled": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Set to `true` to disable the field.\nUse `disabled` to indicate that the field can normally be interacted\nwith, but is currently disabled. This tells the user that if certain\nrequirements are met, the field may become enabled again."
},
"attribute": "disabled",
"reflect": true,
"defaultValue": "false"
},
"readonly": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Set to `true` to make the field read-only.\nUse `readonly` when the field is only there to present the data it holds,\nand will not become possible for the current user to edit."
},
"attribute": "readonly",
"reflect": true,
"defaultValue": "false"
},
"invalid": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Set to `true` to indicate that the current value of the date picker is\ninvalid."
},
"attribute": "invalid",
"reflect": true,
"defaultValue": "false"
},
"label": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Text to display next to the date picker"
},
"attribute": "label",
"reflect": true
},
"placeholder": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "The placeholder text shown inside the input field, when the field is focused and empty"
},
"attribute": "placeholder",
"reflect": true
},
"helperText": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Optional helper text to display below the input field when it has focus"
},
"attribute": "helper-text",
"reflect": true
},
"required": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Set to `true` to indicate that the field is required."
},
"attribute": "required",
"reflect": true,
"defaultValue": "false"
},
"value": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "Date",
"resolved": "Date",
"references": {
"Date": {
"location": "global"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "The value of the field."
}
},
"type": {
"type": "string",
"mutable": false,
"complexType": {
"original": "DateType",
"resolved": "\"date\" | \"datetime\" | \"month\" | \"quarter\" | \"time\" | \"week\" | \"year\"",
"references": {
"DateType": {
"location": "import",
"path": "../date-picker/date.types"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Type of date picker."
},
"attribute": "type",
"reflect": true,
"defaultValue": "'datetime'"
},
"format": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Format to display the selected date in."
},
"attribute": "format",
"reflect": true
},
"language": {
"type": "string",
"mutable": false,
"complexType": {
"original": "Languages",
"resolved": "\"da\" | \"de\" | \"en\" | \"fi\" | \"fr\" | \"nb\" | \"nl\" | \"no\" | \"sv\"",
"references": {
"Languages": {
"location": "import",
"path": "../date-picker/date.types"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Defines the localisation for translations and date formatting.\nProperty `format` customizes the localized date format."
},
"attribute": "language",
"reflect": true,
"defaultValue": "'en'"
},
"formatter": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "(date: Date) => string",
"resolved": "(date: Date) => string",
"references": {
"Date": {
"location": "global"
}
}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Custom formatting function. Will be used for date formatting.\n\n:::note\noverrides `format` and `language`\n:::"
}
}
};
}
static get states() {
return {
"internalFormat": {},
"showPortal": {}
};
}
static get events() {
return [{
"method": "change",
"name": "change",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Emitted when the date picker value is changed."
},
"complexType": {
"original": "Date",
"resolved": "Date",
"references": {
"Date": {
"location": "global"
}
}
}
}];
}
static get elementRef() { return "host"; }
}
//# sourceMappingURL=date-picker.js.map