UNPKG

@true-directive/grid

Version:

Angular Data Grid from Yopsilon.

1,305 lines (1,287 loc) 373 kB
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();