/*
 * @license
 * Copyright Endlessjs. All Rights Reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 */
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
import { Directive, ElementRef, forwardRef, Inject, InjectionToken, Input, ChangeDetectorRef, } from '@angular/core';
import { NG_VALIDATORS, NG_VALUE_ACCESSOR, Validators, } from '@angular/forms';
import { fromEvent, merge } from 'rxjs';
import { map, takeWhile, filter, take, tap } from 'rxjs/operators';
import { EL_DOCUMENT } from '../../theme.options';
import { ElDateService } from '../calendar-kit/services/date.service';
/**
 * The `ElDatepickerAdapter` instances provide way how to parse, format and validate
 * different date types.
 * */
var ElDatepickerAdapter = /** @class */ (function () {
    function ElDatepickerAdapter() {
    }
    return ElDatepickerAdapter;
}());
export { ElDatepickerAdapter };
/**
 * Datepicker is an control that can pick any values anyway.
 * It has to be bound to the datepicker directive through elDatepicker input.
 * */
var ElDatepicker = /** @class */ (function () {
    function ElDatepicker() {
    }
    return ElDatepicker;
}());
export { ElDatepicker };
export var EL_DATE_ADAPTER = new InjectionToken('Datepicker Adapter');
export var EL_DATE_SERVICE_OPTIONS = new InjectionToken('Date service options');
/**
 * The `ElDatepickerDirective` is form control that gives you ability to select dates and ranges. The datepicker
 * is shown when input receives a `focus` event.
 *
 * ```html
 * <input [elDatepicker]="datepicker">
 * <el-datepicker #datepicker></el-datepicker>
 * ```
 *
 * @stacked-example(Showcase, datepicker/datepicker-showcase.component)
 *
 * ### Installation
 *
 * Import `ElDatepickerModule.forRoot()` to your root module.
 * ```ts
 * @NgModule({
 *   imports: [
 *   	// ...
 *     ElDatepickerModule.forRoot(),
 *   ],
 * })
 * export class AppModule { }
 * ```
 * And `ElDatepickerModule` to your feature module.
 * ```ts
 * @NgModule({
 *   imports: [
 *   	// ...
 *     ElDatepickerModule,
 *   ],
 * })
 * export class PageModule { }
 * ```
 * ### Usage
 *
 * If you want to use range selection, you have to use `ElRangepickerComponent` instead:
 *
 * ```html
 * <input [elDatepicker]="rangepicker">
 * <el-rangepicker #rangepicker></el-rangepicker>
 * ```
 *
 * Both range and date pickers support all parameters as calendar, so, check `ElCalendarComponent` for additional
 * info.
 *
 * @stacked-example(Range showcase, datepicker/rangepicker-showcase.component)
 *
 * Datepicker is the form control so it can be bound with angular forms through ngModel and form controls.
 *
 * @stacked-example(Forms, datepicker/datepicker-forms.component)
 *
 * `ElDatepickerDirective` may be validated using `min` and `max` dates passed to the datepicker.
 * And `filter` predicate that receives date object and has to return a boolean value.
 *
 * @stacked-example(Validation, datepicker/datepicker-validation.component)
 *
 * The `ElDatepickerComponent` supports date formatting:
 *
 * ```html
 * <input [elDatepicker]="datepicker">
 * <el-datepicker #datepicker format="MM\dd\yyyy"></el-datepicker>
 * ```
 *
 * ## Formatting Issue
 *
 * By default, datepicker uses angulars `LOCALE_ID` token for localization and `DatePipe` for dates formatting.
 * And native `Date.parse(...)` for dates parsing. But native `Date.parse` function doesn't support formats.
 * To provide custom formatting you have to use one of the following packages:
 *
 * - `@endlessjs/moment` - provides moment date adapter that uses moment for date objects. This means datepicker than
 * will operate only moment date objects. If you want to use it you have to install it: `npm i @endlessjs/moment`, and
 * import `ElMomentDateModule` from this package.
 *
 * - `@endlessjs/date-fns` - adapter for popular date-fns library. This way is preferred if you need only date formatting.
 * Because date-fns is treeshakable, tiny and operates native date objects. If you want to use it you have to
 * install it: `npm i @endlessjs/date-fns`, and import `ElDateFnsDateModule` from this package.
 *
 * ### ElDateFnsDateModule
 *
 * Format is required when using `ElDateFnsDateModule`. You can set it via `format` input on datepicker component:
 * ```html
 * <el-datepicker format="dd.MM.yyyy"></el-datepicker>
 * ```
 * Also format can be set globally with `ElDateFnsDateModule.forRoot({ format: 'dd.MM.yyyy' })` and
 * `ElDateFnsDateModule.forChild({ format: 'dd.MM.yyyy' })` methods.
 *
 * Please note to use some of the formatting tokens you also need to pass `{ awareOfUnicodeTokens: true }` to date-fns
 * parse and format functions. You can configure options passed this functions by setting `formatOptions` and
 * `parseOptions` of options object passed to `ElDateFnsDateModule.forRoot` and `ElDateFnsDateModule.forChild` methods.
 * ```ts
 * ElDateFnsDateModule.forRoot({
 *   parseOptions: { awareOfUnicodeTokens: true },
 *   formatOptions: { awareOfUnicodeTokens: true },
 * })
 * ```
 * Further info on `date-fns` formatting tokens could be found at
 * [date-fns docs](https://date-fns.org/v2.0.0-alpha.27/docs/Unicode-Tokens).
 *
 * You can also use `parseOptions` and `formatOptions` to provide locale.
 * ```ts
 * import { eo } from 'date-fns/locale';
 *
 * @NgModule({
 *   imports: [
 *     ElDateFnsDateModule.forRoot({
 *       parseOptions: { locale: eo },
 *       formatOptions: { locale: eo },
 *     }),
 *   ],
 * })
 * ```
 *
 * @styles
 *
 * datepicker-text-color:
 * datepicker-background-color:
 * datepicker-border-color:
 * datepicker-border-style:
 * datepicker-border-width:
 * datepicker-border-radius:
 * datepicker-shadow:
 * datepicker-arrow-size:
 * */
var ElDatepickerDirective = /** @class */ (function () {
    function ElDatepickerDirective(document, datepickerAdapters, hostRef, dateService, changeDetector) {
        var _this = this;
        this.document = document;
        this.datepickerAdapters = datepickerAdapters;
        this.hostRef = hostRef;
        this.dateService = dateService;
        this.changeDetector = changeDetector;
        this.alive = true;
        this.isDatepickerReady = false;
        this.onChange = function () { };
        this.onTouched = function () { };
        /**
         * Form control validators will be called in validators context, so, we need to bind them.
         * */
        this.validator = Validators.compose([
            this.parseValidator,
            this.minValidator,
            this.maxValidator,
            this.filterValidator,
        ].map(function (fn) { return fn.bind(_this); }));
        this.subscribeOnInputChange();
    }
    ElDatepickerDirective_1 = ElDatepickerDirective;
    Object.defineProperty(ElDatepickerDirective.prototype, "setPicker", {
        /**
         * Provides datepicker component.
         * */
        set: function (picker) {
            this.picker = picker;
            this.setupPicker();
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(ElDatepickerDirective.prototype, "input", {
        /**
         * Returns html input element.
         * */
        get: function () {
            return this.hostRef.nativeElement;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(ElDatepickerDirective.prototype, "inputValue", {
        /**
         * Returns host input value.
         * */
        get: function () {
            return this.input.value;
        },
        enumerable: true,
        configurable: true
    });
    ElDatepickerDirective.prototype.ngOnDestroy = function () {
        this.alive = false;
    };
    /**
     * Writes value in picker and html input element.
     * */
    ElDatepickerDirective.prototype.writeValue = function (value) {
        if (this.isDatepickerReady) {
            this.writePicker(value);
            this.writeInput(value);
        }
        else {
            this.queue = value;
        }
    };
    ElDatepickerDirective.prototype.registerOnChange = function (fn) {
        this.onChange = fn;
    };
    ElDatepickerDirective.prototype.registerOnTouched = function (fn) {
        this.onTouched = fn;
    };
    ElDatepickerDirective.prototype.setDisabledState = function (isDisabled) {
        this.input.disabled = isDisabled;
    };
    /**
     * Form control validation based on picker validator config.
     * */
    ElDatepickerDirective.prototype.validate = function () {
        return this.validator(null);
    };
    /**
     * Hides picker, focuses the input
     */
    ElDatepickerDirective.prototype.hidePicker = function () {
        this.input.focus();
        this.picker.hide();
    };
    /**
     * Validates that we can parse value correctly.
     * */
    ElDatepickerDirective.prototype.parseValidator = function () {
        /**
         * Date services treat empty string as invalid date.
         * That's why we're getting invalid formControl in case of empty input which is not required.
         * */
        if (this.inputValue === '') {
            return null;
        }
        var isValid = this.datepickerAdapter.isValid(this.inputValue, this.picker.format);
        return isValid ? null : { elDatepickerParse: { value: this.inputValue } };
    };
    /**
     * Validates passed value is greater than min.
     * */
    ElDatepickerDirective.prototype.minValidator = function () {
        var config = this.picker.getValidatorConfig();
        var date = this.datepickerAdapter.parse(this.inputValue, this.picker.format);
        return (!config.min || !date || this.dateService.compareDates(config.min, date) <= 0) ?
            null : { elDatepickerMin: { min: config.min, actual: date } };
    };
    /**
     * Validates passed value is smaller than max.
     * */
    ElDatepickerDirective.prototype.maxValidator = function () {
        var config = this.picker.getValidatorConfig();
        var date = this.datepickerAdapter.parse(this.inputValue, this.picker.format);
        return (!config.max || !date || this.dateService.compareDates(config.max, date) >= 0) ?
            null : { elDatepickerMax: { max: config.max, actual: date } };
    };
    /**
     * Validates passed value satisfy the filter.
     * */
    ElDatepickerDirective.prototype.filterValidator = function () {
        var config = this.picker.getValidatorConfig();
        var date = this.datepickerAdapter.parse(this.inputValue, this.picker.format);
        return (!config.filter || !date || config.filter(date)) ?
            null : { elDatepickerFilter: true };
    };
    /**
     * Chooses datepicker adapter based on passed picker component.
     * */
    ElDatepickerDirective.prototype.chooseDatepickerAdapter = function () {
        var _this = this;
        this.datepickerAdapter = this.datepickerAdapters.find(function (_a) {
            var picker = _a.picker;
            return _this.picker instanceof picker;
        });
        if (this.noDatepickerAdapterProvided()) {
            throw new Error('No datepickerAdapter provided for picker');
        }
    };
    /**
     * Attaches picker to the host input element and subscribes on value changes.
     * */
    ElDatepickerDirective.prototype.setupPicker = function () {
        var _this = this;
        this.chooseDatepickerAdapter();
        this.picker.attach(this.hostRef);
        if (this.inputValue) {
            this.picker.value = this.datepickerAdapter.parse(this.inputValue, this.picker.format);
        }
        // In case datepicker component placed after the input with datepicker directive,
        // we can't read `this.picker.format` on first change detection run,
        // since it's not bound yet, so we have to wait for datepicker component initialization.
        if (!this.isDatepickerReady) {
            this.picker.init
                .pipe(takeWhile(function () { return _this.alive; }), take(1), tap(function () { return _this.isDatepickerReady = true; }), filter(function () { return !!_this.queue; }))
                .subscribe(function () {
                _this.writeValue(_this.queue);
                _this.onChange(_this.queue);
                _this.changeDetector.detectChanges();
                _this.queue = undefined;
            });
        }
        this.picker.valueChange
            .pipe(takeWhile(function () { return _this.alive; }))
            .subscribe(function (value) {
            _this.writePicker(value);
            _this.writeInput(value);
            _this.onChange(value);
            if (_this.picker.shouldHide()) {
                _this.hidePicker();
            }
        });
        merge(this.picker.blur, fromEvent(this.input, 'blur').pipe(filter(function () { return !_this.picker.isShown && _this.document.activeElement !== _this.input; }))).pipe(takeWhile(function () { return _this.alive; }), take(1)).subscribe(function () { return _this.onTouched(); });
    };
    ElDatepickerDirective.prototype.writePicker = function (value) {
        this.picker.value = value;
    };
    ElDatepickerDirective.prototype.writeInput = function (value) {
        var stringRepresentation = this.datepickerAdapter.format(value, this.picker.format);
        this.hostRef.nativeElement.value = stringRepresentation;
    };
    /**
     * Validates if no datepicker adapter provided.
     * */
    ElDatepickerDirective.prototype.noDatepickerAdapterProvided = function () {
        return !this.datepickerAdapter || !(this.datepickerAdapter instanceof ElDatepickerAdapter);
    };
    ElDatepickerDirective.prototype.subscribeOnInputChange = function () {
        var _this = this;
        fromEvent(this.input, 'input')
            .pipe(map(function () { return _this.inputValue; }), takeWhile(function () { return _this.alive; }))
            .subscribe(function (value) { return _this.handleInputChange(value); });
    };
    /**
     * Parses input value and write if it isn't null.
     * */
    ElDatepickerDirective.prototype.handleInputChange = function (value) {
        var date = this.parseInputValue(value);
        this.onChange(date);
        this.writePicker(date);
    };
    ElDatepickerDirective.prototype.parseInputValue = function (value) {
        if (this.datepickerAdapter.isValid(value, this.picker.format)) {
            return this.datepickerAdapter.parse(value, this.picker.format);
        }
        return null;
    };
    var ElDatepickerDirective_1;
    __decorate([
        Input('elDatepicker'),
        __metadata("design:type", ElDatepicker),
        __metadata("design:paramtypes", [ElDatepicker])
    ], ElDatepickerDirective.prototype, "setPicker", null);
    ElDatepickerDirective = ElDatepickerDirective_1 = __decorate([
        Directive({
            selector: 'input[elDatepicker]',
            providers: [
                {
                    provide: NG_VALUE_ACCESSOR,
                    useExisting: forwardRef(function () { return ElDatepickerDirective_1; }),
                    multi: true,
                },
                {
                    provide: NG_VALIDATORS,
                    useExisting: forwardRef(function () { return ElDatepickerDirective_1; }),
                    multi: true,
                },
            ],
        }),
        __param(0, Inject(EL_DOCUMENT)),
        __param(1, Inject(EL_DATE_ADAPTER)),
        __metadata("design:paramtypes", [Object, Array, ElementRef,
            ElDateService,
            ChangeDetectorRef])
    ], ElDatepickerDirective);
    return ElDatepickerDirective;
}());
export { ElDatepickerDirective };
//# sourceMappingURL=datepicker.directive.js.map