UNPKG

@limetech/lime-elements

Version:
466 lines (465 loc) • 15 kB
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