@true-directive/grid
Version:
Angular Data Grid from Yopsilon.
1,305 lines (1,287 loc) • 373 kB
JavaScript
import { Internationalization, Selection, GridState, DataQuery, AxInject, Keys, Utils, CloseEvent, Dates, Mask, MaskSectionAction, MaskResult, MaskState, DateParserFormatter, MaskSettings, Column, ColumnType, GridSettings, NumberParserFormatter, KeyInfo, CellHighlighter, RenderMode, GridLayout, Filter, FilterOperator, NumberFormat, RowCalculator, MenuAction, SortType, PagePipe, LazyLoadingMode, CellPosition, DetectionMode, RowLayout, SortInfo, UIAction, GridPart, ContextMenuEvent, RowClickEvent, CellClickEvent, UIActionType, RowDragEvent, ColumnBand, SummaryType } from '@true-directive/base';
export { AxInject, AxInjectConsumer, CellPosition, CellRange, CheckedChangedEvent, Column, ColumnBand, ColumnCollection, ColumnType, DataQuery, Dates, DetectionMode, EditorShowMode, Filter, FilterOperator, GridLayout, GridLayoutRange, GridPart, GridSettings, GridState, GridUIHandler, Keys, LayoutsHandler, LazyLoadingMode, Locale, MenuAction, RenderMode, SelectionMode, SortInfo, SortType, Summary, SummaryPipe, SummaryType, UIAction, UIActionType, Utils, ValueChangedEvent } from '@true-directive/base';
import { __decorate, __metadata, __param } from 'tslib';
import { Injectable, Pipe, Input, HostBinding, Component, forwardRef, EventEmitter, Output, ViewChild, ElementRef, ChangeDetectorRef, Renderer2, Directive, HostListener, Inject, ComponentFactoryResolver, ApplicationRef, Injector, ContentChildren, QueryList, ViewContainerRef, ViewChildren, KeyValueDiffers, NgModule } from '@angular/core';
import { BehaviorSubject, Subject, timer, Observable } from 'rxjs';
import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
import { takeUntil, take } from 'rxjs/operators';
import { CommonModule } from '@angular/common';
let InternationalizationService = class InternationalizationService extends Internationalization {
constructor() {
super();
// On locale change event
this._onLocaleChanged = new BehaviorSubject(this.locale);
this.onLocaleChanged = this._onLocaleChanged.asObservable();
}
localeChangedEvent(l) {
this._onLocaleChanged.next(l);
}
};
InternationalizationService = __decorate([
Injectable(),
__metadata("design:paramtypes", [])
], InternationalizationService);
let TranslatePipe = class TranslatePipe {
constructor(intl) {
this.intl = intl;
}
transform(value) {
return this.intl.translate(value);
}
};
TranslatePipe = __decorate([
Pipe({
name: 'trueTranslate',
pure: false
}),
__metadata("design:paramtypes", [InternationalizationService])
], TranslatePipe);
class GridEvents {
constructor() {
this.name = 'events';
// -- EVENTS -----------------------------------------------------------------
// Запрос данных у родителя
this._onDataQuery = new Subject();
this.onDataQuery = this._onDataQuery.asObservable();
// Получение данных от родителя
this._onDataFetch = new Subject();
this.onDataFetch = this._onDataFetch.asObservable();
// Изменения в списке колонок
this._onColumnsChanged = new Subject();
this.onColumnsChanged = this._onColumnsChanged.asObservable();
// При изменениях в запросе данных (сортировка/фильтр/группировка)
this._onQueryChanged = new Subject();
this.onQueryChanged = this._onQueryChanged.asObservable();
// При изменениях групп (свернута/развернута)
this._onSummariesChanged = new Subject();
this.onSummariesChanged = this._onSummariesChanged.asObservable();
// При изменении значения ячейки
this._onValueChanged = new Subject();
this.onValueChanged = this._onValueChanged.asObservable();
// При изменении чекбокс
this._onCheckedChanged = new Subject();
this.onCheckedChanged = this._onCheckedChanged.asObservable();
// Перетаскивание колонки
// Тащим
this._onDrag = new Subject();
this.onDrag = this._onDrag.asObservable();
// Бросаем
this._onDrop = new Subject();
this.onDrop = this._onDrop.asObservable();
// Изменение ширины колонки
// Тащим
this._onColumnResizing = new Subject();
this.onColumnResizing = this._onColumnResizing.asObservable();
// Бросаем
this._onColumnResize = new Subject();
this.onColumnResize = this._onColumnResize.asObservable();
// При фильтрации
this._onFilterShow = new Subject();
this.onFilterShow = this._onFilterShow.asObservable();
// Выделение ячейки/строки/области
this._onSelect = new Subject();
this.onSelect = this._onSelect.asObservable();
// Включение редактора
this._onStartEditing = new Subject();
this.onStartEditing = this._onStartEditing.asObservable();
// Выключение редактора
this._onStopEditing = new Subject();
this.onStopEditing = this._onStopEditing.asObservable();
// Строка перестала быть видимой после редактирования
this._onRowUnfiltered = new Subject();
this.onRowUnfiltered = this._onRowUnfiltered.asObservable();
// Контекстное меню колонки
this._onHeaderContextMenu = new Subject();
this.onHeaderContextMenu = this._onHeaderContextMenu.asObservable();
// Событие кастомной ячейки
this._onCustomCellEvent = new Subject();
this.onCustomCellEvent = this._onCustomCellEvent.asObservable();
}
dataQueryEvent(query) {
this._onDataQuery.next(query);
}
dataFetchEvent(query) {
if (query.subject) {
query.subject.next();
query.subject.complete();
}
this._onDataFetch.next(query);
}
columnsChangedEvent() {
this._onColumnsChanged.next();
}
queryChangedEvent(query) {
this._onQueryChanged.next(query);
}
summariesChangedEvent(c) {
this._onSummariesChanged.next(c);
}
valueChangedEvent(e) {
this._onValueChanged.next(e);
}
checkedChangedEvent(e) {
this._onCheckedChanged.next(e);
}
dragEvent(e) {
this._onDrag.next(e);
}
dropEvent(e) {
this._onDrop.next(e);
}
columnResizeEvent(e) {
this._onColumnResize.next(e);
}
filterShowEvent(e) {
this._onFilterShow.next(e);
}
selectEvent(cp) {
this._onSelect.next(cp);
}
startEditingEvent(cp) {
this._onStartEditing.next(cp);
}
stopEditingEvent(returnFocus) {
this._onStopEditing.next(returnFocus);
}
headerContextMenuEvent(e) {
this._onHeaderContextMenu.next(e);
}
customCellEvent(e) {
this._onCustomCellEvent.next(e);
}
}
/**
* Copyright (c) 2018-2019 Aleksey Melnikov, True Directive Company.
* @link https://truedirective.com/
* @license MIT
*/
class GridSelection extends Selection {
constructor() {
super();
// Изменен фокус
this._onFocusChanged = new Subject();
this.onFocusChanged = this._onFocusChanged.asObservable();
// Изменено выделение. Аргумент - последняя позиция последнего range
this._onSelectionChanged = new Subject();
this.onSelectionChanged = this._onSelectionChanged.asObservable();
}
selectionChangedEvent(cp) {
this._onSelectionChanged.next(cp);
}
focusChangedEvent(cp) {
this._onFocusChanged.next(cp);
}
}
class DOMUtils {
static focusAndOpenKeyboard(el, timeout) {
if (el) {
// Align temp input element approximately where the input element is
// so the cursor doesn't jump around
var __tempEl__ = document.createElement('input');
__tempEl__.style.position = 'absolute';
__tempEl__.style.top = (el.offsetTop + 7) + 'px';
__tempEl__.style.left = el.offsetLeft + 'px';
__tempEl__.style.height = '0';
__tempEl__.style.opacity = '0';
// Put this temp element as a child of the page <body> and focus on it
document.body.appendChild(__tempEl__);
__tempEl__.focus();
// The keyboard is open. Now do a delayed focus on the target element
setTimeout(function () {
el.focus();
el.click();
// Remove the temp element
document.body.removeChild(__tempEl__);
}, timeout);
}
}
static downloadCSV(filename, text) {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/csv;charset=utf-8,%EF%BB%BF' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
static copyToClipboard(text) {
var ta = document.createElement("textarea");
ta.value = text;
ta.style.position = 'fixed';
ta.style.opacity = '0.0';
ta.style.width = '20px';
ta.style.height = '20px';
ta.style.top = '-40px';
ta.style.left = '-40px';
document.body.appendChild(ta);
ta.focus();
ta.select();
try {
var successful = document.execCommand('copy');
return successful;
}
catch (err) {
return false;
}
document.body.removeChild(ta);
}
}
let GridStateService =
// @AxInjectConsumer
class GridStateService extends GridState {
constructor(internationalization) {
super();
this.internationalization = internationalization;
this.focusChangedSubscription = this.selection.onFocusChanged.subscribe(v => {
this.layoutsHandler.updateLayoutSelections(v);
this.focusChanged(v);
});
// При изменении выделения - обновить в лэйаутах. Но сейчас наоборот
this.selectionChangedSubscription = this.selection.onSelectionChanged.subscribe(v => {
this.layoutsHandler.updateLayoutSelections(v);
this.events.selectEvent(v);
});
this.localeChangedSubscription = this.internationalization.onLocaleChanged.subscribe(locale => {
this.dataSource.valueFormatter.setLocale(locale);
});
}
// Инициируем обновление данных со всеми пересчётами
updateDataAsync() {
const subject = new Subject();
if (this.settings.requestData) {
// Необходимо запросить данные
this.doQuery(subject);
// НО! Нужно обновить колонки.
this.events.columnsChangedEvent();
return subject;
}
// Запрашивать не нужно, считаем всё сами
// Асинхронное обновление
this.recalcData().then(() => {
this.fetchData(new DataQuery(this._dataQueryCounter));
let rc;
if (this.dataSource.resultRows) {
rc = this.dataSource.resultRows.length;
}
subject.next(rc);
subject.complete();
});
return subject;
}
copySelectionToClipboard(withHeaders) {
DOMUtils.copyToClipboard(this.getSelectedData(this.selection).toString(withHeaders, '\t'));
}
exportToCSV(fileName, columnSeparator = ',') {
DOMUtils.downloadCSV(fileName, this.dataToExport().toString(true, columnSeparator, true));
}
ngOnDestroy() {
this.focusChangedSubscription.unsubscribe();
this.selectionChangedSubscription.unsubscribe();
this.localeChangedSubscription.unsubscribe();
}
/*
// Важно обновить выделенные области в layouts
protected subscribe() {
this.events.onSelect.subscribe((cp: CellPosition) => {
this.layoutsHandler.updateLayoutSelections(cp);
});
}
*/
registerHandlers() {
super.registerHandlers();
this.handlers['events'] = GridEvents;
this.handlers['selection'] = GridSelection;
}
};
__decorate([
AxInject('events'),
__metadata("design:type", GridEvents)
], GridStateService.prototype, "events", void 0);
__decorate([
AxInject('selection'),
__metadata("design:type", GridSelection)
], GridStateService.prototype, "selection", void 0);
GridStateService = __decorate([
Injectable()
// @AxInjectConsumer
,
__metadata("design:paramtypes", [InternationalizationService])
], GridStateService);
var CheckboxComponent_1;
/**
* Checkbox component
*/
let CheckboxComponent = CheckboxComponent_1 = class CheckboxComponent {
/**
* Checkbox component
*/
constructor() {
this.onChange = (_) => { };
this.onTouched = () => { };
this.caption = '';
}
registerOnChange(fn) { this.onChange = fn; }
registerOnTouched(fn) { this.onTouched = fn; }
get value() {
return this._value;
}
;
set value(v) {
if (v !== this._value) {
this._value = v;
this.onChange(v);
}
}
get inversed() {
return this._inversed;
}
blur() {
this.onTouched();
}
// Show value. Formatter: Ctrl --> View
writeValue(value) {
if (this._value !== value) {
this._value = value;
}
}
};
__decorate([
Input('caption'),
__metadata("design:type", String)
], CheckboxComponent.prototype, "caption", void 0);
__decorate([
Input('inversed'),
__metadata("design:type", Object)
], CheckboxComponent.prototype, "_inversed", void 0);
__decorate([
HostBinding('class.inversed'),
__metadata("design:type", Object),
__metadata("design:paramtypes", [])
], CheckboxComponent.prototype, "inversed", null);
CheckboxComponent = CheckboxComponent_1 = __decorate([
Component({
selector: 'true-checkbox',
template: `
<true-checkbox-wrapper [class.inversed]="inversed">
<input type="checkbox" [(ngModel)]="value" (blur)="blur()"/>
<span caption>{{caption}}</span>
</true-checkbox-wrapper>
`,
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CheckboxComponent_1),
multi: true
}]
})
], CheckboxComponent);
/**
* Checkbox wrapper component
*/
let CheckboxWrapperComponent = class CheckboxWrapperComponent {
};
CheckboxWrapperComponent = __decorate([
Component({
selector: "true-checkbox-wrapper",
template: `<label class="true-checkbox"><ng-content select="[caption]"></ng-content>
<ng-content select="input"></ng-content>
<span class="true-checkbox__checkmark"></span>
</label>`
})
], CheckboxWrapperComponent);
let InputWrapperComponent = class InputWrapperComponent {
constructor() {
this.showError = true;
this.disabled = null;
this.onBtnClick = new EventEmitter();
}
get hasBtn() {
return this.icon !== undefined && this.icon !== "";
}
get hasError() {
return this.error !== undefined && this.error !== "";
}
btnClick(e) {
this.onBtnClick.emit(e);
e.stopPropagation();
}
};
__decorate([
HostBinding('class.true-input_with-btn'),
__metadata("design:type", Object),
__metadata("design:paramtypes", [])
], InputWrapperComponent.prototype, "hasBtn", null);
__decorate([
HostBinding('class.true-input_with-error'),
__metadata("design:type", Object),
__metadata("design:paramtypes", [])
], InputWrapperComponent.prototype, "hasError", null);
__decorate([
Input('icon'),
__metadata("design:type", String)
], InputWrapperComponent.prototype, "icon", void 0);
__decorate([
Input('error'),
__metadata("design:type", String)
], InputWrapperComponent.prototype, "error", void 0);
__decorate([
Input('showError'),
__metadata("design:type", Boolean)
], InputWrapperComponent.prototype, "showError", void 0);
__decorate([
Input('disabled'),
__metadata("design:type", Boolean)
], InputWrapperComponent.prototype, "disabled", void 0);
__decorate([
Output('btnClick'),
__metadata("design:type", EventEmitter)
], InputWrapperComponent.prototype, "onBtnClick", void 0);
InputWrapperComponent = __decorate([
Component({
selector: 'true-input-wrapper',
// It is important that the button follows the content without line breaking.
// Otherwise a suspicious margin to the right of the button appears.
//
// Inner DIV with display=flex to avoid line breaking if the width of the component = 100%
template: `
<div>
<ng-content></ng-content><button
*ngIf="icon"
type="button"
tabindex="-1"
class="true-input__btn"
[attr.disabled]="disabled"
(click)="btnClick($event)">
<div [ngClass]="icon"></div>
</button>
</div>
<div *ngIf="showError" class="true-input__err-msg">{{error}}</div>
`,
host: { 'class': 'true-input' },
styles: [`
:host {
overflow-x: visible;
word-wrap: normal;
display: inline-block;
vertical-align: baseline;
padding: 0;
margin: 0;
}
:host > div:first-child {
width: 100%;
height: 100%;
display: inline-flex;
}
.true-input__err-msg {
position: absolute;
display: none;
}
:host.true-input_with-error > .true-input__err-msg {
display: block;
}
`]
})
], InputWrapperComponent);
var PopupComponent_1;
let PopupComponent = PopupComponent_1 = class PopupComponent {
constructor(elementRef, changeDetector, _renderer) {
this.elementRef = elementRef;
this.changeDetector = changeDetector;
this._renderer = _renderer;
this.transform0 = 'translateX(15px)';
this.transform1 = 'translateX(0)';
this.modalTransform0 = 'translateY(-20px)';
this.modalTransform1 = 'translateY(0)';
this.modalTransform2 = 'translateY(20px)';
this.snackTransform0 = 'scale(0.85)';
this.snackTransform1 = 'scale(1.0)';
this.snackTransform2 = 'scale(1.5)';
// Number of pixels for shifting the popup to right when position is [left].
this.shiftDx = 6;
this.close = new EventEmitter();
this.closed = new EventEmitter();
this.show = new EventEmitter();
this.position = 'RELATIVE';
this.keepOnTargetClick = true;
this._x = -1;
this._y = -1;
this._visible = false;
this._stillVisible = false;
this._animating = false;
this._overlay = null;
}
getZ() {
return this.zIndex;
}
/**
* Focus trap
* Looking for the next element to switch focus.
* @param element Элемент, относительно которого нужно найти следующий
* @param backward Поиск назад (Shift+Tab)
* @param parent Родительский, в котором сейчас ищем
* @param found Заданный элемент найден. Берем следующий подходящий
* @return Элемент, на который следует перевести фокус
*/
getNextElement(element, backward = false, parent = null, found = false) {
if (element === null) {
// Элемент, не задан, ищем первый попавшийся
found = true;
}
if (parent === null) {
// Родительский элемент не задан, ищем в хосте
parent = this.popup.nativeElement;
}
for (let i = 0; i < parent.children.length; i++) {
const el = backward ? parent.children[parent.children.length - i - 1] : parent.children[i];
if (el.hidden || el.disabled) {
continue;
}
if (el === element) {
found = true;
continue;
}
if (el.offsetParent === null) {
continue;
}
if (found && el.tabIndex !== -1 && (el.nodeName === 'INPUT'
|| el.nodeName === 'BUTTON'
|| el.nodeName === 'SELECT'
|| el.nodeName === 'TEXTAREA'
|| el.tabIndex > 0)) {
return { found: found, element: el };
}
const res = this.getNextElement(element, backward, el, found);
found = res.found;
if (res.element) {
return res;
}
}
return { found: found, element: null };
}
popupMouseDown(e) {
if (this.zIndex >= PopupComponent_1.z) {
e.stopPropagation();
}
}
popupTouchStart(e) {
e.stopPropagation();
}
popupKeyDown(e) {
if (e.keyCode === Keys.ESCAPE) {
this.closePopup();
e.preventDefault();
e.stopPropagation();
}
if (e.keyCode === Keys.TAB) {
// Ищем элемент, на который мы можем отправить фокус после target
let res = this.getNextElement(e.target, e.shiftKey);
// Не найдено после заданного? Ищем первый
if (res.element === null) {
res = this.getNextElement(null, e.shiftKey);
}
if (res.element) {
res.element.focus();
}
e.preventDefault();
e.stopPropagation();
}
}
addDocumentListeners() {
if (!this.documentContextMenuBound) {
this.documentContextMenuBound = this.documentContextMenu.bind(this);
}
if (!this.documentMouseDownBound) {
this.documentMouseDownBound = this.documentMouseDown.bind(this);
}
if (!this.documentTouchStartBound) {
this.documentTouchStartBound = this.documentTouchStart.bind(this);
}
if (!this.documentScrollBound) {
this.documentScrollBound = this.documentScroll.bind(this);
}
if (!this.documentResizeBound) {
this.documentResizeBound = this.documentResize.bind(this);
}
document.addEventListener('contextmenu', this.documentContextMenuBound, false);
document.addEventListener('mousedown', this.documentMouseDownBound, false);
document.addEventListener('touchstart', this.documentTouchStartBound, false);
window.addEventListener('scroll', this.documentScrollBound, false);
window.addEventListener('resize', this.documentResizeBound, false);
}
removeDocumentListeners() {
document.removeEventListener('contextmenu', this.documentContextMenuBound, false);
document.removeEventListener('mousedown', this.documentMouseDownBound, false);
document.removeEventListener('touchstart', this.documentTouchStartBound, false);
window.removeEventListener('scroll', this.documentScrollBound, false);
window.removeEventListener('resize', this.documentResizeBound, false);
}
maxZIndex(element) {
let z = 0;
var parent = element.parentNode;
while (parent && parent.style) {
if (!isNaN(parent.style.zIndex) && parent.style.zIndex > z) {
z = parent.style.zIndex;
}
parent = parent.parentNode;
}
return +z;
}
documentScroll(e) {
this.updatePosition();
}
documentResize(e) {
this.updatePosition();
}
checkClose(target) {
const l = target;
if (this._target === l && this.keepOnTargetClick) {
return false;
}
if (this._target && Utils.isAncestor(this._target, l) && this.keepOnTargetClick) {
return false;
}
if (Utils.isAncestor(this.popup.nativeElement, l)) {
return false;
}
else {
if (this.zIndex < this.maxZIndex(l)) {
// Мы кликнули на более высокий уровень
return false;
}
}
if (PopupComponent_1.freeze > 0) {
PopupComponent_1.freeze--;
return false;
}
this.closePopup();
return true;
}
documentTouchStart(e) {
this.checkClose(e.target);
}
documentMouseDown(e) {
this.checkClose(e.target);
}
documentContextMenu(e) {
this.checkClose(e.target);
}
get visible() {
return this._visible;
}
makeOverlay() {
PopupComponent_1.z++;
const zIndex = PopupComponent_1.z;
this._overlay = this._renderer.createElement('div');
this._renderer.setStyle(this._overlay, 'z-index', zIndex);
this._renderer.addClass(this._overlay, 'true-modal-overlay');
this._renderer.appendChild(document.body, this._overlay);
this._renderer.listen(this._overlay, 'touchstart', (e) => {
this.closePopup();
e.stopPropagation();
e.preventDefault();
});
this._renderer.listen(this._overlay, 'mousedown', (e) => {
this.closePopup();
e.stopPropagation();
e.preventDefault();
});
setTimeout(() => {
this._renderer.setStyle(this._overlay, 'opacity', '1.0');
}, 50);
return this._overlay;
}
removeOverlay() {
if (this._overlay) {
document.body.removeChild(this._overlay);
PopupComponent_1.z--;
}
this._overlay = null;
}
resetPosition() {
this.popup.nativeElement.style.transform = 'scale(1.0)';
this.popup.nativeElement.style.top = '0px';
this.popup.nativeElement.style.left = '0px';
}
updatePosition() {
if (this._x !== -1 || this._y !== -1) {
if (PopupComponent_1.renderToBody) {
this.popup.nativeElement.style.position = 'fixed';
}
this.popup.nativeElement.style.left = this._x + 'px';
this.popup.nativeElement.style.top = this._y + 'px';
return;
}
if (this.position === 'ABSOLUTE') {
this.popup.nativeElement.style.position = 'absolute';
this.popup.nativeElement.style.top = 'unset';
this.popup.nativeElement.style.left = 'unset';
return;
}
const popupRect = this.popup.nativeElement.getBoundingClientRect();
if (this.position === 'MODAL' || this.position === 'SNACK') {
const ww = document.body.clientWidth;
let width = popupRect.width;
let modalX = (ww - width) / 2;
if (modalX <= 10) {
modalX = 10;
width = ww - 20;
}
this.popup.nativeElement.style.left = modalX + 'px';
this.popup.nativeElement.style.top = '35px';
return;
}
let targetRect = this._target.getBoundingClientRect();
let xx = targetRect.left;
let yy = targetRect.bottom;
if (this._direction.toLowerCase() === 'left') {
xx = targetRect.right - popupRect.width + this.shiftDx;
}
if (this._direction.toLowerCase() === 'right') {
xx = targetRect.right;
yy = targetRect.top - this.shiftDx;
}
if (yy + popupRect.height > window.innerHeight && this._direction !== 'right') {
yy = targetRect.top - popupRect.height;
}
if (yy + popupRect.height > window.innerHeight && this._direction === 'right') {
yy = targetRect.bottom - popupRect.height + 4;
}
if (this._direction === 'AboveLeft') {
xx = targetRect.right - popupRect.width + 6;
yy = targetRect.top - popupRect.height;
}
if (this._direction === 'AboveRight') {
xx = targetRect.left - 6;
yy = targetRect.top - popupRect.height;
}
if (xx + popupRect.width > window.innerWidth) {
xx = window.innerWidth - popupRect.width - 4;
}
else {
xx = xx < 0 ? 4 : xx;
}
yy = yy < 0 ? 4 : yy;
this.popup.nativeElement.style.position = 'fixed';
this.popup.nativeElement.style.left = xx + 'px';
this.popup.nativeElement.style.top = yy + 'px';
}
resetAnimation() {
let t0 = this.transform0;
if (this.position === 'MODAL') {
t0 = this.modalTransform0;
}
if (this.position === 'SNACK') {
t0 = this.snackTransform0;
}
this.popup.nativeElement.style.opacity = '0';
this.popup.nativeElement.style.transform = t0;
}
startAnimation() {
let t1 = this.transform1;
if (this.position === 'MODAL') {
t1 = this.modalTransform1;
}
if (this.position === 'SNACK') {
t1 = this.snackTransform1;
}
this.popup.nativeElement.style.opacity = '1.0';
this.popup.nativeElement.style.transform = t1;
}
display() {
if (this._visible) {
// To prevent the Z-index from being updated during false closures.
return;
}
this._visible = true;
this.popup.nativeElement.style.display = 'none';
this.resetAnimation();
this.resetPosition();
setTimeout(() => {
if (this.position === 'MODAL' || this.position === 'SNACK') {
this.popup.nativeElement.style.position = 'fixed';
this.popup.nativeElement.style.display = 'block';
if (this.position === 'MODAL') {
this.makeOverlay();
this._overlay.appendChild(this.popup.nativeElement);
this.resetAnimation();
}
else {
this._renderer.removeChild(this.elementRef.nativeElement, this.popup.nativeElement);
this.changeDetector.detectChanges();
document.body.appendChild(this.popup.nativeElement);
this.resetAnimation();
}
this.updatePosition();
}
else {
this.popup.nativeElement.style.display = 'block';
this.updatePosition();
if (this.position === 'RELATIVE' && PopupComponent_1.renderToBody) {
this.popup.nativeElement.style.opacity = '0';
this._renderer.removeChild(this.elementRef.nativeElement, this.popup.nativeElement);
this.changeDetector.detectChanges();
document.body.appendChild(this.popup.nativeElement);
}
}
PopupComponent_1.z++;
this.zIndex = PopupComponent_1.z;
this.popup.nativeElement.style.zIndex = this.zIndex;
this.resetAnimation();
setTimeout(() => {
this.startAnimation();
if (this.position === 'SNACK') {
this.closeSnack();
}
}, 50);
this.addDocumentListeners();
this.show.emit();
});
}
closeSnack() {
this._stillVisible = true;
setTimeout(() => {
if (this._stillVisible) {
this.popup.nativeElement.style.opacity = '0';
this.popup.nativeElement.style.transform = this.snackTransform2;
setTimeout(() => {
this.closePopup();
}, 300);
}
}, 1000);
}
showByXY(x, y) {
this._x = x;
this._y = y;
this.display();
}
showByTarget(target = null, direction = '') {
this._target = target;
this._direction = direction;
this.display();
}
showPopup() {
if (this._visible) {
this.closePopup();
}
this.showByTarget();
}
closePopup(result = null, confirmed = false) {
if (!this._visible) {
return; // Чтобы Z-индекс не обновлялся при ложных закрытиях
}
this._visible = false;
this._stillVisible = false;
// можно отменить закрытие
const event = new CloseEvent(result);
event.confirmed = confirmed;
this.close.emit(event);
if (event.isCanceled) {
return;
}
PopupComponent_1.z--;
if (PopupComponent_1.z <= 9) {
PopupComponent_1.z = 9;
}
if (this.position === 'MODAL') {
this._overlay.removeChild(this.popup.nativeElement);
this.removeOverlay();
this.elementRef.nativeElement.appendChild(this.popup.nativeElement);
}
if (this.position === 'SNACK') {
document.body.removeChild(this.popup.nativeElement);
this.elementRef.nativeElement.appendChild(this.popup.nativeElement);
}
this._target = null;
this._x = -1;
this._y = -1;
this.popup.nativeElement.style.display = 'none';
this.resetAnimation();
if (this.position === 'RELATIVE' && PopupComponent_1.renderToBody) {
this._renderer.removeChild(document.body, this.popup.nativeElement);
this.changeDetector.detectChanges();
this.elementRef.nativeElement.appendChild(this.popup.nativeElement);
}
else {
this.changeDetector.detectChanges();
}
this.removeDocumentListeners();
this.closed.emit(result);
}
toggle(target, direction) {
if (this._visible) {
this.closePopup();
}
else {
this.showByTarget(target, direction);
}
}
};
// Popup will not be closed if value of this property more than 0
PopupComponent.freeze = 0;
PopupComponent.z = 19;
PopupComponent.renderToBody = true;
__decorate([
ViewChild('popup', { static: true }),
__metadata("design:type", Object)
], PopupComponent.prototype, "popup", void 0);
__decorate([
Output('close'),
__metadata("design:type", EventEmitter)
], PopupComponent.prototype, "close", void 0);
__decorate([
Output('closed'),
__metadata("design:type", EventEmitter)
], PopupComponent.prototype, "closed", void 0);
__decorate([
Output('show'),
__metadata("design:type", EventEmitter)
], PopupComponent.prototype, "show", void 0);
__decorate([
Input('position'),
__metadata("design:type", String)
], PopupComponent.prototype, "position", void 0);
__decorate([
Input('keepOnTargetClick'),
__metadata("design:type", Object)
], PopupComponent.prototype, "keepOnTargetClick", void 0);
PopupComponent = PopupComponent_1 = __decorate([
Component({
selector: 'true-popup',
template: `
<div [style.zIndex]="getZ()" class="true-popup"
[class.true-snack]="position==='SNACK'"
(mousedown)="popupMouseDown($event)"
(touchstart)="popupTouchStart($event)"
(keydown)="popupKeyDown($event)" #popup>
<ng-content #content></ng-content>
</div>`,
host: {
'(touchend)': '$event.stopPropagation()'
},
styles: [`
:host > div {
position: fixed;
display: none;
opacity: 0.0;
}
.true-modal-overlay {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
opacity: 0.0;
overflow-y: auto;
}
`]
}),
__metadata("design:paramtypes", [ElementRef,
ChangeDetectorRef,
Renderer2])
], PopupComponent);
var CalendarComponent_1;
/**
* Calendar component.
*/
let CalendarComponent = CalendarComponent_1 = class CalendarComponent {
constructor(intl, cd, el) {
this.intl = intl;
this.cd = cd;
this.el = el;
this.onChange = (_) => { };
this.onTouched = () => { };
this._value = null;
this.mode = 'days';
this.dateClick = new EventEmitter();
this.escape = new EventEmitter();
// Day names
this.dayNames = [];
// Month weeks
this.weeks = [];
// Year months
this.monthRows = [];
// Matrix of displayed years
this.yearRows = [];
this._minYear = 0;
this._maxYear = 0;
}
get value() {
return this._value;
}
set value(v) {
if (v !== this._value) {
this._value = v;
this.createWeeks(this._value);
this.onChange(v);
}
}
get valueTime() {
let vTime = 0;
if (this.value !== null && !isNaN(this.value.getTime())) {
vTime = this.value.getTime();
}
return vTime;
}
get monthYear() {
const m = this.calendarDateStart.getMonth();
const y = this.calendarDateStart.getFullYear();
if (this.mode === 'days') {
return this.intl.locale.longMonthNames[m] + ' ' + y;
}
if (this.mode === 'months') {
return y;
}
if (this.mode === 'years') {
return this._minYear + ' - ' + this._maxYear;
}
}
get today() {
return Dates.today();
}
registerOnChange(fn) { this.onChange = fn; }
registerOnTouched(fn) { this.onTouched = fn; }
blur() {
this.onTouched();
}
// Отображаем значение в компоненте. Formatter: Ctrl --> View
writeValue(value) {
if (this.value !== value) {
this.value = value;
}
}
setFocus() {
this.days.nativeElement.focus();
}
isCurrentMonth(d) {
return Dates.dateBetween(d, this.calendarDateStart, this.calendarDateEnd);
}
go(qty) {
if (this.mode === 'days') {
let newDate;
if (qty > 0) {
newDate = Dates.firstDateOfNextMonth(this.calendarDateStart);
}
else {
newDate = Dates.firstDateOfPrevMonth(this.calendarDateStart);
}
this.createWeeks(newDate);
}
if (this.mode === 'months') {
this.calendarDateStart = new Date(this.calendarDateStart.getFullYear() + qty, 0, 1);
this.createMonths();
}
if (this.mode === 'years') {
this.calendarDateStart = new Date(this.calendarDateStart.getFullYear() + 24 * qty, 0, 1);
this.createYears();
}
}
toggleMode() {
if (this.mode === 'years') {
this.mode = 'days';
this.createWeeks(this.calendarDateStart);
}
else {
if (this.mode === 'months') {
this.mode = 'years';
this.createYears();
}
else {
if (this.mode === 'days') {
this.mode = 'months';
this.createMonths();
}
}
}
this.setMode();
}
setMode() {
this.el.nativeElement.classList.remove('true-mode-days');
this.el.nativeElement.classList.remove('true-mode-months');
this.el.nativeElement.classList.remove('true-mode-years');
this.el.nativeElement.classList.add('true-mode-' + this.mode);
}
mousedown(e) {
e.stopPropagation();
}
calendarDateClick(e, d) {
if (this.mode === 'days') {
this.value = new Date(d);
this.dateClick.emit(d);
}
if (this.mode === 'months') {
this.mode = 'days';
this.createWeeks(new Date(d));
}
if (this.mode === 'years') {
this.calendarDateStart = new Date(d);
this.mode = 'months';
this.createMonths();
}
this.setMode();
e.preventDefault();
e.stopPropagation();
}
// List of dates in selected month
createDayNames() {
this.dayNames = [];
const weekStart = Dates.firstDateOfWeek(Dates.today(), this.intl.locale.firstDayOfWeek);
const weekEnd = Dates.lastDateOfWeek(weekStart, this.intl.locale.firstDayOfWeek);
for (let d = new Date(weekStart); d.getTime() <= weekEnd.getTime(); d = Dates.nextDate(d)) {
const wd = d.getDay();
const dayName = this.intl.locale.shortDayNames[wd];
this.dayNames.push(dayName);
}
}
// List of months to show in months-mode
createMonths() {
const yy = this.calendarDateStart.getFullYear();
const currentMonthStart = Dates.firstDateOfMonth(Dates.today());
let k = -3;
this.monthRows = [];
for (let i = 0; i < 6; i++) {
let monthRow = [];
for (let j = 0; j < 3; j++) {
// Month start
const dd = new Date(yy, k, 1);
monthRow.push({ name: this.intl.locale.shortMonthNames[dd.getMonth()] + ' ' + Dates.yearTwoDigits(dd),
date: dd,
selected: Dates.isSameMonth(dd, this.value),
today: dd.getTime() === currentMonthStart.getTime(),
current: Dates.isSameYear(dd, this.calendarDateStart) });
k++;
}
this.monthRows.push(monthRow);
}
}
// List of dates in selected month
createYears() {
const yy = this.calendarDateStart.getFullYear();
const currentYearStart = new Date(Dates.today().getFullYear(), 0, 1);
const calendarYearStart = new Date(this.calendarDateStart.getFullYear(), 0, 1);
this._minYear = yy - 11;
let k = this._minYear;
this.yearRows = [];
for (let i = 0; i < 6; i++) {
let yearRow = [];
for (let j = 0; j < 4; j++) {
const dd = new Date(k, 0, 1);
yearRow.push({ name: k + '',
date: dd,
selected: Dates.isSameYear(dd, this.value),
today: dd.getTime() === currentYearStart.getTime(),
current: true });
k++;
}
this.yearRows.push(yearRow);
}
this._maxYear = k - 1;
}
createWeeks(date) {
if (date === null || isNaN(date.getTime())) {
date = Dates.today();
}
const firstDayOfWeek = this.intl.locale.firstDayOfWeek;
const monthStart = Dates.firstDateOfMonth(date);
const monthEnd = Dates.lastDateOfMonth(date);
if (this.weeks.length > 0
&& this.calendarDateStart !== undefined && this.calendarDateStart.getTime() === monthStart.getTime()
&& this.calendarDateEnd !== undefined && this.calendarDateEnd.getTime() === monthEnd.getTime()) {
return;
}
this.weeks = [];
this.calendarDateStart = monthStart;
this.calendarDateEnd = monthEnd;
const calendarStart = Dates.firstDateOfWeek(monthStart, firstDayOfWeek);
// Iterating weeks of month
let weekStart = new Date(calendarStart);
let wCounter = 0;
while (wCounter < 6) {
let week = [];
let weekEnd = Dates.lastDateOfWeek(weekStart, firstDayOfWeek);
// Iterating days of week
for (let d = new Date(weekStart); d.getTime() <= weekEnd.getTime(); d = Dates.nextDate(d)) {
week.push(d);
}
this.weeks.push(week);
// Next week
weekStart = Dates.nextDate(weekEnd);
wCounter++;
}
}
daysKeyDown(e) {
let dd = 0;
if (e.keyCode === Keys.LEFT) {
dd = -1;
}
if (e.keyCode === Keys.UP) {
dd = -7;
}
if (e.keyCode === Keys.RIGHT) {
dd = 1;
}
if (e.keyCode === Keys.DOWN) {
dd = 7;
}
if (e.keyCode === Keys.ENTER) {
this.dateClick.emit(this.value);
e.stopPropagation();
}
if (e.keyCode === Keys.ESCAPE) {
this.escape.emit(this.value);
e.stopPropagation();
}
if (dd !== 0) {
this.value = Dates.addDays(this.value === null ? Dates.today() : this.value, dd);
e.stopPropagation();
}
}
ngOnInit() {
this.createDayNames();
this.createWeeks(Dates.today());
}
};
__decorate([
ViewChild('days', { static: true }),
__metadata("design:type", Object)
], CalendarComponent.prototype, "days", void 0);
__decorate([
Output('dateClick'),
__metadata("design:type", EventEmitter)
], CalendarComponent.prototype, "dateClick", void 0);
__decorate([
Output('escape'),
__metadata("design:type", EventEmitter)
], CalendarComponent.prototype, "escape", void 0);
CalendarComponent = CalendarComponent_1 = __decorate([
Component({
selector: 'true-calendar',
template: "<!-- Buttons -->\r\n<div class=\"true-calendar__controls\">\r\n <div>\r\n <button class=\"true-button\" (click)=\"toggleMode()\" ><b>{{monthYear}}</b></button>\r\n </div>\r\n <div>\r\n <button class=\"true-button prev\" (click)=\"go(-1)\"><span class=\"true-icon-left-open\"></span></button>\r\n <button class=\"true-button next\" (click)=\"go(1)\"><span class=\"true-icon-right-open\"></span></button>\r\n </div>\r\n</div>\r\n\r\n<!-- Day names in header -->\r\n<table class=\"true-day-names\">\r\n <colGroup>\r\n <col *ngFor=\"let d of dayNames\" />\r\n </colGroup>\r\n <thead>\r\n <tr>\r\n <td *ngFor=\"let d of dayNames\" [class.true-transparent]=\"mode!='days'\">{{d}}</td>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n <tr><td colspan=\"7\"></td></tr>\r\n </tbody>\r\n</table>\r\n\r\n<!-- Month days -->\r\n<table #days class=\"true-days\" tabindex=\"1\" (keydown)=\"daysKeyDown($event)\">\r\n <colGroup>\r\n <col *ngFor=\"let d of dayNames\" />\r\n </colGroup>\r\n <tbody>\r\n <tr *ngFor=\"let w of weeks\">\r\n <td *ngFor=\"let d of w\"\r\n (click)=\"calendarDateClick($event, d)\"\r\n [class.true-selected]=\"d.getTime() === valueTime\"\r\n [class.true-today]=\"d.getTime() === today.getTime()\"\r\n [class.true-current]=\"isCurrentMonth(d)\">{{d.getDate()}}</td>\r\n </tr>\r\n </tbody>\r\n</table>\r\n<!-- Months -->\r\n<table class=\"true-months\">\r\n <colGroup>\r\n <col *ngFor=\"let r of monthRows[0]\" />\r\n </colGroup>\r\n <tbody>\r\n <tr *ngFor=\"let r of monthRows\">\r\n <td *ngFor=\"let m of r\"\r\n (click)=\"calendarDateClick($event, m.date)\"\r\n [class.true-transparent]=\"m.hide\"\r\n [class.true-selected]=\"m.selected\"\r\n [class.true-today]=\"m.today\"\r\n [class.true-current]=\"m.current\">{{m.name}}</td>\r\n </tr>\r\n </tbody>\r\n</table>\r\n<!-- Years -->\r\n<table class=\"true-years\">\r\n <colGroup>\r\n <col *ngFor=\"let r of yearRows[0]\" />\r\n </colGroup>\r\n <tbody>\r\n <tr *ngFor=\"let r of yearRows\">\r\n <td *ngFor=\"let y of r\"\r\n (click)=\"calendarDateClick($event, y.date)\"\r\n [class.true-selected]=\"y.selected\"\r\n [class.true-today]=\"y.today\"\r\n [class.true-current]=\"1==1\">{{y.name}}</td>\r\n </tr>\r\n </tbody>\r\n</table>\r\n",
host: {
'class': 'true-calendar true-mode-days',
'(mousedown)': 'mousedown($event)',
'(touchend)': '$event.stopPropagation()'
},
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CalendarComponent_1),
multi: true
}],
styles: [":host>table{table-layout:fixed;border-collapse:separate}:host>table:focus{outline:0}:host table.true-days,:host table.true-months,:host table.true-years{display:none}:host.true-mode-days table.true-days,:host.true-mode-months table.true-months,:host.true-mode-years table.true-years{display:table}.true-calendar__controls{display:-webkit-box;display:flex;flex-wrap:nowrap;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-direction:row;-webkit-box-pack:justify;justify-content:space-between}.true-calendar__controls div:last-child{display:-webkit-box;display:flex;flex-wrap:nowrap;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-direction:row}"]
}),
__metadata("design:paramtypes", [InternationalizationService,
ChangeDetectorRef,
ElementRef])
], CalendarComponent);
/**
* Dropdown base component.
*/
let DropdownBaseComponent = class DropdownBaseComponent {
constructor(_elementRef, _renderer) {
this._elementRef = _elementRef;
this._renderer = _renderer;
this.usePopup = true;
this.currentPopupVisible = false;
this.disableTextEditor = false;
this.disabled = null;
this.maxDropDownHeight = '300px';
this.blur = new EventEmitter();
this.keydown = new EventEmitter();