@transunion-ui/tablejs
Version:
Tablejs ========
1,063 lines • 178 kB
JavaScript
import { Directive, ContentChild, EventEmitter, Inject, Input, Output } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { TablejsForOfContext } from './../../directives/virtual-for/virtual-for.directive';
import { ScrollPrevSpacerComponent } from '../../components/scroll-prev-spacer/scroll-prev-spacer.component';
import { take } from 'rxjs';
import * as i0 from "@angular/core";
import * as i1 from "./../../services/grid/grid.service";
import * as i2 from "./../../services/directive-registration/directive-registration.service";
import * as i3 from "./../../services/scroll-dispatcher/scroll-dispatcher.service";
import * as i4 from "./../../services/operating-system/operating-system.service";
export class ScrollViewportDirective {
constructor(elementRef, gridService, document, directiveRegistrationService, scrollDispatcherService, operatingSystem, cdr, rendererFactory) {
this.elementRef = elementRef;
this.gridService = gridService;
this.document = document;
this.directiveRegistrationService = directiveRegistrationService;
this.scrollDispatcherService = scrollDispatcherService;
this.operatingSystem = operatingSystem;
this.cdr = cdr;
this.rendererFactory = rendererFactory;
this.templateRef = null;
this.templateID = '';
this.generateCloneMethod = null;
this._arrowUpSpeed = 1;
this._arrowDownSpeed = 1;
this._preItemOverflow = 1;
this._postItemOverflow = 1;
this._itemLoadLimit = Infinity;
this.items = null;
// Custom Elements Inputs
this.templateid = null;
this.preitemoverflow = 1;
this.postitemoverflow = 1;
this.arrowupspeed = 1;
this.arrowdownspeed = 1;
this.itemloadlimit = Infinity;
this.itemAdded = new EventEmitter();
this.itemRemoved = new EventEmitter();
this.itemUpdated = new EventEmitter();
this.rangeUpdated = new EventEmitter();
this.viewportScrolled = new EventEmitter();
this.viewportReady = new EventEmitter();
this.viewportInitialized = new EventEmitter();
this.containerHeight = null;
this.heightLookup = {};
this.itemVisibilityLookup = {};
this.listElm = null;
this.listContent = null;
this.prevSpacer = null;
this.postSpacer = null;
this.gridDirective = null;
this.pauseViewportRenderUpdates = false;
this.range = { startIndex: 0, endIndex: 1, extendedStartIndex: 0, extendedEndIndex: 1 };
this.lastRange = { startIndex: this.range.startIndex, endIndex: this.range.endIndex, extendedStartIndex: this.range.extendedStartIndex, extendedEndIndex: this.range.extendedEndIndex };
this.lastScrollTop = 0;
this.currentScrollTop = 0;
this.currentScrollChange = 0;
this.template = null;
this.estimatedFullContentHeight = 0;
this.estimatedPreListHeight = 0;
this.estimatedPostListHeight = 0;
this.totalItemsCounted = 0;
this.totalHeightCount = 0;
this.itemName = '';
this.overflowHeightCount = 0;
this.scrollChangeByFirstIndexedItem = 0;
this.lastVisibleItemHeight = Infinity;
this.adjustedStartIndex = null;
this.forcedEndIndex = undefined;
this.placeholderObject = {};
this.postItemOverflowCount = -1;
this.preItemOverflowCount = -1;
this.lastVisibleItemOverflow = 0;
this.preOverflowHeight = 0;
this.mouseIsOverViewport = false;
this.lastHeight = 0;
this.observer = null;
this.handleMouseOver = null;
this.handleMouseOut = null;
this.handleKeyDown = null;
this.cloneFromTemplateRef = false;
this.viewportHasScrolled = false;
this.templateContext = null;
this.virtualNexus = null;
this._cloneMethod = null;
this.onTransitionEnd = (e) => {
};
this.onTransitionRun = (e) => {
};
this.onTransitionStart = (e) => {
};
this.onTransitionCancel = (e) => {
};
this.renderer = this.rendererFactory.createRenderer(null, null);
this.elementRef.nativeElement.scrollViewportDirective = this;
}
get arrowUpSpeed() {
return Number(this._arrowUpSpeed);
}
set arrowUpSpeed(value) {
this._arrowUpSpeed = Number(value);
}
get arrowDownSpeed() {
return Number(this._arrowDownSpeed);
}
set arrowDownSpeed(value) {
this._arrowDownSpeed = Number(value);
}
get preItemOverflow() {
return Number(this._preItemOverflow);
}
set preItemOverflow(value) {
this._preItemOverflow = Number(value);
}
get postItemOverflow() {
return Number(this._postItemOverflow);
}
set postItemOverflow(value) {
this._postItemOverflow = Number(value);
}
get itemLoadLimit() {
return Number(this._itemLoadLimit);
}
set itemLoadLimit(value) {
this._itemLoadLimit = Number(value);
}
handleScroll(e) {
e.preventDefault();
this.currentScrollTop = this.listContent.scrollTop;
this.currentScrollChange = this.currentScrollTop - this.lastScrollTop;
this.scrollChangeByFirstIndexedItem += this.currentScrollChange;
this.lastVisibleItemOverflow -= this.currentScrollChange;
const newRange = this.getRangeChange(this.scrollChangeByFirstIndexedItem);
this.updateScrollFromRange(newRange);
this.scrollDispatcherService.dispatchViewportScrolledEvents(this.viewportScrolled, this.lastScrollTop, this.scrollChangeByFirstIndexedItem, this, this.elementRef.nativeElement);
}
registerViewportToElement() {
this.elementRef.nativeElement.scrollViewport = this;
}
attachMutationObserver() {
const ths = this;
this.observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
ths.updateMutations(mutation);
});
});
this.observer.observe(this.listContent, {
// configure it to listen to attribute changes
attributes: true,
subtree: true,
childList: true
});
}
updateMutations(mutation) {
if (mutation.type === 'childList') {
const addedNodes = Array.from(mutation.addedNodes);
addedNodes.forEach(node => {
this.directiveRegistrationService.registerNodeAttributes(node);
this.getChildNodes(node);
});
}
}
getChildNodes(node) {
node.childNodes.forEach(childNode => {
this.directiveRegistrationService.registerNodeAttributes(childNode);
if (childNode.childNodes) {
this.getChildNodes(childNode);
}
});
}
registerCustomElementsInputs(viewport) {
this.templateID = viewport.getAttribute('templateID');
this.preItemOverflow = Number(viewport.getAttribute('preItemOverflow'));
this.postItemOverflow = Number(viewport.getAttribute('postItemOverflow'));
this.itemLoadLimit = Number(viewport.getAttribute('itemLoadLimit'));
this.arrowUpSpeed = Number(viewport.getAttribute('arrowUpSpeed'));
this.arrowDownSpeed = Number(viewport.getAttribute('arrowDownSpeed'));
this.fillViewportScrolling = viewport.getAttribute('fillViewportScrolling');
}
convertCustomElementsVariables() {
if (this.templateid) {
this.templateID = this.templateid;
}
if (this.preitemoverflow) {
this.preItemOverflow = Number(this.preitemoverflow);
}
if (this.postitemoverflow) {
this.postItemOverflow = Number(this.postitemoverflow);
}
if (this.arrowdownspeed) {
this.arrowDownSpeed = Number(this.arrowdownspeed);
}
if (this.arrowupspeed) {
this.arrowUpSpeed = Number(this.arrowupspeed);
}
if (this.itemloadlimit !== null) {
this.itemLoadLimit = Number(this.itemloadlimit);
}
}
createTBodies() {
this.listElm = this.elementRef.nativeElement;
let body = this.listElm.getElementsByTagName('tbody')[0];
if (body) {
body = body.getAttribute('tablejsViewport') !== null ? body : null;
}
this.listContent = body ? body : document.createElement('tbody');
this.listContent.setAttribute('tablejsListContent', '');
this.listContent.setAttribute('tablejsViewport', '');
this.listContent.style.display = 'block';
this.listContent.style.position = 'relative';
this.listContent.style.height = '350px';
this.listContent.style.overflowY = 'auto';
this.listElm.appendChild(this.listContent);
if (this.fillViewportScrolling !== undefined && this.fillViewportScrolling !== null) {
const coverBody = document.createElement('tbody');
coverBody.style.display = 'block';
coverBody.style.position = 'absolute';
coverBody.style.width = '100%';
coverBody.style.height = '100%';
coverBody.style.overflow = 'auto';
coverBody.style.pointerEvents = 'none';
coverBody.style.visibility = 'false';
this.listElm.appendChild(coverBody);
}
this.directiveRegistrationService.registerViewportOnGridDirective(this.listContent);
const componentRef = this.virtualNexus.virtualForDirective._viewContainer.createComponent(ScrollPrevSpacerComponent);
this.virtualNexus.virtualForDirective._viewContainer.detach(0);
const ref = this.virtualNexus.virtualForDirective._viewContainer.createEmbeddedView(componentRef.instance.template, undefined, 0);
componentRef.destroy();
this.prevSpacer = ref.rootNodes[0];
this.postSpacer = document.createElement('tr');
this.postSpacer.setAttribute('tablejsPostSpacer', '');
this.postSpacer.style.display = 'block';
this.postSpacer.style.position = 'relative';
this.listContent.appendChild(this.postSpacer);
}
addScrollHandler() {
this.listContent.addEventListener('scroll', this.handleListContentScroll = (e) => {
this.handleScroll(e);
});
}
rerenderRowAt(index, updateScrollPosition = false) {
if (!this.viewportHasScrolled) {
return;
}
const ind = index - this.adjustedStartIndex;
const itemName = 'item' + index;
if (ind > this.items.length - 1 || this.itemVisibilityLookup[this.itemName] !== true) {
return;
}
const indexMap = {};
for (let i = 1; i < this.virtualNexus.virtualForDirective._viewContainer.length; i++) {
indexMap[this.virtualNexus.virtualForDirective._viewContainer.get(i).rootNodes[0].index] = i;
}
;
const detachedRef = this.virtualNexus.virtualForDirective._viewContainer.detach(indexMap[index]);
const child = detachedRef.rootNodes[0];
detachedRef.destroy();
this.templateContext = new TablejsForOfContext(this.items[index], this.virtualNexus.virtualForDirective._tablejsForOf, index, this.items.length);
const ref = this.virtualNexus.virtualForDirective._viewContainer.createEmbeddedView(this.virtualNexus.virtualForDirective._template, this.templateContext, indexMap[index]);
this.virtualNexus.virtualForDirective._viewContainer.move(ref, indexMap[index]);
let clone = ref.rootNodes[0];
clone.index = index;
this.cdr.detectChanges();
this.scrollDispatcherService.dispatchRemoveItemEvents(this.itemRemoved, child, index, this, this.elementRef.nativeElement);
const lookupHeight = clone.offsetHeight;
const oldHeight = this.heightLookup[itemName];
this.heightLookup[itemName] = lookupHeight;
clone.lastHeight = lookupHeight;
this.addResizeSensor(clone, index);
if (oldHeight) {
this.updateEstimatedHeightFromResize(oldHeight, lookupHeight);
}
else {
this.updateEstimatedHeight(lookupHeight);
}
if (updateScrollPosition) {
this.refreshViewport();
}
this.scrollDispatcherService.dispatchUpdateItemEvents(this.itemUpdated, clone, index, this, this.elementRef.nativeElement);
this.scrollDispatcherService.dispatchAddItemEvents(this.itemAdded, clone, index, this, this.elementRef.nativeElement);
}
viewportRendered() {
this.virtualNexus = this.directiveRegistrationService.getVirtualNexusFromViewport(this);
if (this.virtualNexus && this.virtualNexus.virtualForDirective) {
this.items = this.virtualNexus.virtualForDirective._tablejsForOf;
this.virtualForChangesSubscription$ = this.virtualNexus.virtualForDirective.changes.subscribe(item => {
const isTheSameArray = this.items === item.tablejsForOf;
this.items = item.tablejsForOf;
const scrollToOptions = { index: 0, scrollAfterIndexedItem: 0 };
if (isTheSameArray) {
scrollToOptions.index = this.range.startIndex;
scrollToOptions.scrollAfterIndexedItem = this.scrollChangeByFirstIndexedItem;
// array has changed...rerender current elements
const listChildren = Array.from(this.listContent.childNodes);
}
else {
this.updateItems(item.tablejsForOf, scrollToOptions);
}
});
}
this.createTBodies();
this.addScrollHandler();
if (this.items && (this.generateCloneMethod || this.virtualNexus.virtualForDirective._template)) {
this.initScroll({
items: this.items,
generateCloneMethod: this._cloneMethod
});
}
this.scrollDispatcherService.dispatchViewportReadyEvents(this.viewportReady, this, this.elementRef.nativeElement);
}
scrollToBottom() {
this.range.startIndex = this.items.length;
this.scrollToExact(this.range.startIndex, 0);
}
scrollToTop() {
this.scrollToExact(0, 0);
}
pageUp() {
let heightCount = this.scrollChangeByFirstIndexedItem;
if (this.range.startIndex === 0) {
this.scrollToExact(0, 0);
return;
}
for (let i = this.range.startIndex - 1; i >= 0; i--) {
const lookupHeight = this.heightLookup['item' + i] ? this.heightLookup['item' + i] : this.avgItemHeight;
heightCount += lookupHeight;
if (heightCount >= this.containerHeight || i === 0) {
const overflowDifference = heightCount >= this.containerHeight ? heightCount - this.containerHeight : 0;
this.scrollToExact(i, overflowDifference);
break;
}
}
}
pageDown() {
this.range.startIndex = this.range.endIndex - 1;
const overflowDifference = this.heightLookup['item' + (this.range.endIndex - 1).toString()] - this.lastVisibleItemOverflow;
this.scrollToExact(this.range.startIndex, overflowDifference);
}
addArrowListeners() {
this.elementRef.nativeElement.addEventListener('mouseenter', this.handleMouseOver = (e) => {
this.mouseIsOverViewport = true;
});
this.elementRef.nativeElement.addEventListener('mouseleave', this.handleMouseOut = (e) => {
this.mouseIsOverViewport = false;
});
document.addEventListener('keydown', this.handleKeyDown = (e) => {
if (this.mouseIsOverViewport) {
const isMac = this.operatingSystem.isMac();
switch (e.code) {
case 'ArrowDown':
if (isMac && e.metaKey) {
e.preventDefault();
this.scrollToBottom();
}
else {
e.preventDefault();
this.range.startIndex += Number(this.arrowDownSpeed);
this.scrollToExact(this.range.startIndex, 0);
}
break;
case 'ArrowUp':
if (isMac && e.metaKey) {
e.preventDefault();
this.scrollToTop();
}
else {
if (this.scrollChangeByFirstIndexedItem === 0) {
e.preventDefault();
this.range.startIndex -= Number(this.arrowUpSpeed);
this.scrollToExact(this.range.startIndex, 0);
}
else {
e.preventDefault();
this.scrollChangeByFirstIndexedItem = 0;
this.scrollToExact(this.range.startIndex, 0);
}
}
break;
case 'PageDown':
e.preventDefault();
this.pageDown();
break;
case 'PageUp':
e.preventDefault();
this.pageUp();
break;
case 'End':
e.preventDefault();
this.scrollToBottom();
break;
case 'Home':
e.preventDefault();
this.scrollToTop();
break;
}
}
});
}
ngAfterViewInit() {
this.gridDirective = this.gridService.getParentTablejsGridDirective(this.elementRef.nativeElement)['gridDirective'];
this.gridDirective.scrollViewportDirective = this;
this.preGridInitializeSubscription$ = this.gridDirective.preGridInitialize.pipe(take(1)).subscribe(res => {
this.cdr.detectChanges();
this.refreshContainerHeight();
this.refreshViewport();
// placeholder object is used only to initialize first grid render
if (this.items[0] === this.placeholderObject) {
this.items.shift();
}
});
this.viewportRendered();
this.addArrowListeners();
}
ngOnInit() {
this.registerViewportToElement();
this._cloneMethod = this.generateCloneMethod;
}
ngOnDestroy() {
this.listElm = null;
if (this.virtualNexus && this.virtualNexus.virtualForDirective) {
this.virtualNexus.virtualForDirective._viewContainer.detach(0);
this.virtualNexus.virtualForDirective._viewContainer.clear();
}
this.items = [];
this.elementRef.nativeElement.scrollViewport = null;
this.templateRef = null;
this._cloneMethod = null;
this.generateCloneMethod = null;
if (this.virtualNexus) {
this.directiveRegistrationService.clearVirtualNexus(this.virtualNexus);
this.virtualNexus.virtualForDirective = null;
this.virtualNexus.scrollViewportDirective = null;
this.virtualNexus = null;
}
clearTimeout(this.timeoutID);
this.elementRef.nativeElement.removeEventListener('mouseenter', this.handleMouseOver);
this.elementRef.nativeElement.removeEventListener('mouseleave', this.handleMouseOut);
if (this.listContent) {
this.listContent.removeEventListener('scroll', this.handleListContentScroll);
}
this.handleListContentScroll = null;
document.removeEventListener('keydown', this.handleKeyDown);
if (this.virtualForChangesSubscription$) {
this.virtualForChangesSubscription$.unsubscribe();
}
if (this.preGridInitializeSubscription$) {
this.preGridInitializeSubscription$.unsubscribe();
}
this.elementRef.nativeElement.scrollViewportDirective = null;
}
setScrollSpacers() {
const numItemsAfterShownList = this.items.length - this.range.extendedEndIndex;
const numItemsBeforeShownList = this.adjustedStartIndex;
const totalUnshownItems = numItemsBeforeShownList + numItemsAfterShownList;
const beforeItemHeightPercent = totalUnshownItems !== 0 ? numItemsBeforeShownList / totalUnshownItems : 0;
const afterItemHeightPercent = totalUnshownItems !== 0 ? numItemsAfterShownList / totalUnshownItems : 0;
const remainingHeight = this.estimatedFullContentHeight - this.lastHeight;
this.estimatedPreListHeight = Math.round(beforeItemHeightPercent * remainingHeight);
this.estimatedPostListHeight = Math.round(afterItemHeightPercent * remainingHeight);
// account for rounding both up
this.estimatedPostListHeight = this.estimatedPostListHeight - (afterItemHeightPercent * remainingHeight) === 0.5 ? this.estimatedPostListHeight - 1 : this.estimatedPostListHeight;
if (this.forcedEndIndex) {
this.estimatedPreListHeight = 0;
this.estimatedPostListHeight = 0;
}
this.prevSpacer.style.height = this.estimatedPreListHeight.toString() + 'px';
this.postSpacer.style.height = this.estimatedPostListHeight.toString() + 'px';
}
setHeightByListHeightDifference(liHeight, listHeight) {
return liHeight - listHeight;
}
removePreScrollItems(lastIndex, index) {
if (lastIndex < index) {
for (let i = lastIndex; i < index; i++) {
const firstRef = this.virtualNexus.virtualForDirective._viewContainer.get(1);
if (firstRef) {
const firstChild = firstRef.rootNodes[0];
const itemName = 'item' + i;
this.itemVisibilityLookup[itemName] = false;
const detachedRef = this.virtualNexus.virtualForDirective._viewContainer.detach(1);
detachedRef.destroy();
this.cdr.detectChanges();
this.removeResizeSensor(firstChild, i);
this.lastHeight -= this.heightLookup[itemName];
this.scrollDispatcherService.dispatchRemoveItemEvents(this.itemRemoved, firstChild, i, this, this.elementRef.nativeElement);
}
}
}
}
removePostScrollItems(lastEndIndex, endIndex) {
if (lastEndIndex >= this.items.length) {
lastEndIndex = this.items.length - 1;
}
for (let i = lastEndIndex; i >= endIndex; i--) {
const lastChild = this.getPreviousSibling(this.listContent.lastElementChild);
if (lastChild) {
const itemName = 'item' + i;
this.itemVisibilityLookup[itemName] = false;
const detachedRef = this.virtualNexus.virtualForDirective._viewContainer.detach(this.virtualNexus.virtualForDirective._viewContainer.length - 1);
detachedRef.destroy();
this.cdr.detectChanges();
this.removeResizeSensor(lastChild, i);
this.lastHeight -= this.heightLookup[itemName];
this.scrollDispatcherService.dispatchRemoveItemEvents(this.itemRemoved, detachedRef.rootNodes[0], i, this, this.elementRef.nativeElement);
}
}
}
updateItems(items, scrollToOptions = { index: -1, scrollAfterIndexedItem: 0 }) {
if (this.pauseViewportRenderUpdates) {
return;
}
for (let i = this.virtualNexus.virtualForDirective._viewContainer.length - 1; i > 0; i--) {
const detachedRef = this.virtualNexus.virtualForDirective._viewContainer.detach(i);
detachedRef.destroy();
}
this.cdr.detectChanges();
this.resetToInitialValues();
this.items = items;
if (this.virtualNexus) {
this.virtualNexus.virtualForDirective._tablejsForOf = items;
}
if (scrollToOptions.index !== -1) {
this.scrollToExact(scrollToOptions.index, scrollToOptions.scrollAfterIndexedItem);
}
}
resetToInitialValues() {
this.lastScrollTop = 0;
this.currentScrollTop = 0;
this.currentScrollChange = 0;
this.estimatedFullContentHeight = 0;
this.estimatedPreListHeight = 0;
this.estimatedPostListHeight = 0;
this.totalItemsCounted = 0;
this.totalHeightCount = 0;
this.avgItemHeight = undefined;
this.heightLookup = {};
this.itemVisibilityLookup = {};
this.overflowHeightCount = 0;
this.scrollChangeByFirstIndexedItem = 0;
this.lastVisibleItemHeight = Infinity;
this.preOverflowHeight = 0;
this.lastHeight = 0;
this.range.startIndex = 0;
this.range.endIndex = 0;
this.range.extendedStartIndex = 0;
this.range.extendedEndIndex = 0;
this.lastRange.startIndex = this.range.startIndex;
this.lastRange.endIndex = this.range.endIndex;
this.lastRange.extendedStartIndex = this.range.extendedStartIndex;
this.lastRange.extendedEndIndex = this.range.extendedEndIndex;
this.forcedEndIndex = undefined;
}
recalculateRowHeight(index) {
const itemName = 'item' + index;
const indexMap = {};
for (let i = 1; i < this.virtualNexus.virtualForDirective._viewContainer.length; i++) {
indexMap[this.virtualNexus.virtualForDirective._viewContainer.get(i).rootNodes[0].index] = i;
}
;
const rowRef = this.virtualNexus.virtualForDirective._viewContainer.get(indexMap[index]);
const rowEl = rowRef.rootNodes[0];
const lookupHeight = rowEl.offsetHeight;
const heightDifference = lookupHeight - this.heightLookup[itemName];
this.updateEstimatedHeightFromResize(this.heightLookup[itemName], lookupHeight);
this.heightLookup[itemName] = lookupHeight;
rowEl.lastHeight = lookupHeight;
this.lastHeight += heightDifference;
}
updateEstimatedHeightFromResize(oldHeight, newHeight) {
this.totalHeightCount += (newHeight - oldHeight);
this.avgItemHeight = (this.totalHeightCount / this.totalItemsCounted);
this.estimatedFullContentHeight = this.avgItemHeight * this.items.length;
}
updateEstimatedHeight(height) {
this.totalHeightCount += height;
this.totalItemsCounted++;
this.avgItemHeight = (this.totalHeightCount / this.totalItemsCounted);
this.estimatedFullContentHeight = this.avgItemHeight * this.items.length;
}
getPreviousSibling(el) {
if (!el) {
return null;
}
let prev = el.previousSibling;
while (prev !== null && prev !== undefined && prev.nodeType !== 1) {
prev = prev.previousSibling;
}
return prev;
}
getNextSibling(el) {
if (!el) {
return null;
}
let next = el.nextSibling;
while (next !== null && next !== undefined && next.nodeType !== 1) {
next = next.nextSibling;
}
return next;
}
getEstimatedChildInsertions(remainingHeight) {
return Math.ceil(remainingHeight / this.avgItemHeight);
}
setLastRangeToCurrentRange() {
this.lastRange.startIndex = this.range.startIndex;
this.lastRange.endIndex = this.range.endIndex;
this.lastRange.extendedStartIndex = this.range.extendedStartIndex;
this.lastRange.extendedEndIndex = this.range.extendedEndIndex;
}
resetLastHeight() {
if (!this.lastHeight) {
this.lastHeight = 0;
}
}
maintainIndexInBounds(index) {
if (index > this.items.length - 1) {
index = this.items.length - 1;
}
else if (index < 0) {
index = 0;
}
return index;
}
maintainEndIndexInBounds(index) {
if (index > this.items.length) {
index = this.items.length;
}
else if (index < 0) {
index = 0;
}
return index;
}
showRange(startIndex, endIndex, overflow = 0) {
this.updateItems(this.items, { index: startIndex, scrollAfterIndexedItem: endIndex });
startIndex = this.maintainIndexInBounds(startIndex);
endIndex = this.maintainEndIndexInBounds(endIndex);
if (endIndex <= startIndex) {
endIndex = startIndex + 1;
}
const oldContainerHeight = this.containerHeight;
const oldPreItemOverflow = Number(this.preItemOverflow);
const oldPostItemOverflow = Number(this.postItemOverflow);
this.preItemOverflow = 0;
this.postItemOverflow = 0;
this.containerHeight = 100000;
this.forcedEndIndex = endIndex;
this.scrollToExact(startIndex, overflow);
const rangeToKeep = { ...this.range };
const lastRangeToKeep = { ...this.lastRange };
this.preItemOverflow = oldPreItemOverflow;
this.postItemOverflow = oldPostItemOverflow;
this.containerHeight = oldContainerHeight;
this.forcedEndIndex = undefined;
this.range = rangeToKeep;
this.lastRange = lastRangeToKeep;
}
getDisplayedContentsHeight() {
return this.lastHeight;
}
refreshContainerHeight() {
this.containerHeight = this.listContent.clientHeight;
}
allItemsFitViewport(recalculateContainerHeight = false, refreshViewport = false) {
if (recalculateContainerHeight) {
this.cdr.detectChanges();
this.refreshContainerHeight();
}
if (refreshViewport) {
this.refreshViewport(true);
}
return this.range.startIndex === this.range.extendedStartIndex &&
this.range.endIndex === this.range.extendedEndIndex &&
this.lastHeight <= this.containerHeight;
}
getCurrentScrollPosition() {
return {
index: this.range.startIndex,
overflow: this.scrollChangeByFirstIndexedItem,
lastItemOverflow: this.lastVisibleItemOverflow > 0 ? 0 : this.lastVisibleItemOverflow
};
}
setHeightsForOverflowCalculations(itemIndex, scrollToIndex, itemHeight) {
this.lastHeight += itemHeight;
if (itemIndex < scrollToIndex) {
this.preOverflowHeight += itemHeight;
}
if (itemIndex >= scrollToIndex) {
this.overflowHeightCount += itemHeight;
if (this.overflowHeightCount >= this.containerHeight) {
this.postItemOverflowCount++;
if (this.postItemOverflowCount === 0) {
this.lastVisibleItemHeight = this.heightLookup['item' + itemIndex];
}
}
}
}
addResizeSensor(el, index) {
}
removeResizeSensor(el, index) {
}
getCloneFromTemplateRef(index) {
let clone;
this.templateContext = new TablejsForOfContext(this.items[index], this.virtualNexus.virtualForDirective._tablejsForOf, index, this.items.length);
const viewRef = this.virtualNexus.virtualForDirective._template.createEmbeddedView(this.templateContext);
viewRef.detectChanges();
clone = viewRef.rootNodes[0];
return clone;
}
addScrollItems(index, overflow) {
const scrollingUp = index < this.lastRange.startIndex;
this.range.extendedStartIndex = this.adjustedStartIndex;
this.range.startIndex = index;
this.overflowHeightCount = -overflow;
this.preOverflowHeight = 0;
const firstEl = this.getNextSibling(this.listContent.firstElementChild);
this.lastHeight = 0;
let batchSize = this.avgItemHeight !== undefined && isNaN(this.avgItemHeight) === false ? this.getEstimatedChildInsertions(this.containerHeight - this.lastHeight) + Number(this.preItemOverflow) + Number(this.postItemOverflow) : 1;
let itemsToBatch = [];
let itemBefore;
let indexBefore;
const firstRef = this.virtualNexus.virtualForDirective._viewContainer.get(1);
const appendToEnd = firstRef === null;
for (let i = this.adjustedStartIndex; i < this.adjustedStartIndex + Number(this.itemLoadLimit); i++) {
if (i < 0) {
continue;
}
if (i > this.items.length - 1) {
break;
}
this.itemName = 'item' + i;
// only insert item if it is not already visible
const itemIsInvisible = this.itemVisibilityLookup[this.itemName] !== true;
if (itemIsInvisible) {
itemBefore = !scrollingUp ? this.postSpacer : firstEl;
indexBefore = !scrollingUp || appendToEnd ? this.virtualNexus.virtualForDirective._viewContainer.length : this.virtualNexus.virtualForDirective._viewContainer.indexOf(firstRef);
this.itemVisibilityLookup[this.itemName] = true;
this.templateContext = new TablejsForOfContext(this.items[i], this.virtualNexus.virtualForDirective._tablejsForOf, i, this.items.length);
const ref = this.virtualNexus.virtualForDirective._viewContainer.createEmbeddedView(this.virtualNexus.virtualForDirective._template, this.templateContext, indexBefore);
this.virtualNexus.virtualForDirective._viewContainer.move(ref, indexBefore);
const prev = ref.rootNodes[0];
prev.index = i;
itemsToBatch.push({ index: i, name: this.itemName, item: prev, before: itemBefore });
this.scrollDispatcherService.dispatchAddItemEvents(this.itemAdded, prev, i, this, this.elementRef.nativeElement);
}
else {
itemsToBatch.push({ index: i, name: this.itemName, item: null, before: null });
this.setHeightsForOverflowCalculations(i, index, this.heightLookup[this.itemName]);
}
if (itemsToBatch.length === batchSize || i === this.items.length - 1 || this.postItemOverflowCount >= Number(this.postItemOverflow)) {
for (let j = 0; j < itemsToBatch.length; j++) {
const batchObj = itemsToBatch[j];
const name = batchObj.name;
const ind = batchObj.index;
const oldHeight = this.heightLookup[name];
if (batchObj.item === null) {
continue;
}
this.cdr.detectChanges();
const lookupHeight = batchObj.item.offsetHeight;
this.heightLookup[name] = lookupHeight;
batchObj.item.lastHeight = lookupHeight;
this.addResizeSensor(batchObj.item, batchObj.index);
if (oldHeight) {
this.updateEstimatedHeightFromResize(oldHeight, lookupHeight);
}
else {
this.updateEstimatedHeight(lookupHeight);
}
this.setHeightsForOverflowCalculations(ind, index, lookupHeight);
}
batchSize = this.getEstimatedChildInsertions(this.containerHeight - this.lastHeight) + Number(this.preItemOverflow) + Number(this.postItemOverflow);
if (batchSize <= 0) {
batchSize = Number(this.postItemOverflow);
}
itemsToBatch = [];
}
if (this.postItemOverflowCount <= 0) {
this.range.endIndex = i + 1;
}
this.range.extendedEndIndex = i + 1;
// if item height is lower than the bottom of the container area, stop adding items
if (this.forcedEndIndex === undefined) {
if (this.postItemOverflowCount >= Number(this.postItemOverflow)) {
break;
}
}
else {
if (i === this.forcedEndIndex - 1) {
break;
}
}
}
let itemName;
let endIndexFound = false;
let heightCount = -overflow;
for (let i = this.range.startIndex; i < this.range.extendedEndIndex; i++) {
itemName = 'item' + i;
heightCount += this.heightLookup[itemName];
if (this.forcedEndIndex !== undefined) {
if (i === this.forcedEndIndex - 1) {
this.range.endIndex = i + 1;
this.lastVisibleItemOverflow = heightCount - this.containerHeight;
endIndexFound = true;
break;
}
}
else {
if (heightCount >= this.containerHeight && !endIndexFound) {
this.range.endIndex = i + 1;
this.lastVisibleItemOverflow = heightCount - this.containerHeight;
endIndexFound = true;
break;
}
}
}
}
addMissingPostScrollItemsAndUpdateOverflow(index, overflow) {
let firstEl;
let itemsToBatch = [];
let batchSize;
if (this.overflowHeightCount <= this.containerHeight) {
batchSize = this.getEstimatedChildInsertions(this.containerHeight) + Number(this.preItemOverflow);
this.preItemOverflowCount = -1;
this.preOverflowHeight = 0;
firstEl = this.getNextSibling(this.listContent.firstElementChild);
let heightCount = 0;
let count = 0;
for (let i = this.range.endIndex - 1; i >= 0; i--) {
this.itemName = 'item' + i;
count++;
if (i <= this.range.extendedStartIndex && this.itemVisibilityLookup[this.itemName] !== true) {
this.itemVisibilityLookup[this.itemName] = true;
this.templateContext = new TablejsForOfContext(this.items[i], this.virtualNexus.virtualForDirective._tablejsForOf, i, this.items.length);
const ref = this.virtualNexus.virtualForDirective._viewContainer.createEmbeddedView(this.virtualNexus.virtualForDirective._template, this.templateContext, 1);
this.virtualNexus.virtualForDirective._viewContainer.move(ref, 1);
const prev = ref.rootNodes[0];
prev.index = i;
this.cdr.detectChanges();
itemsToBatch.push({ index: i, name: this.itemName, item: prev, before: firstEl });
this.scrollDispatcherService.dispatchAddItemEvents(this.itemAdded, prev, i, this, this.elementRef.nativeElement);
firstEl = prev;
this.range.extendedStartIndex = i;
this.adjustedStartIndex = i;
}
else {
itemsToBatch.push({ index: i, name: this.itemName, item: null, before: null });
heightCount += this.heightLookup[this.itemName];
if (heightCount > this.containerHeight) {
this.preItemOverflowCount++;
if (this.preItemOverflowCount === 0) {
overflow = heightCount - this.containerHeight;
this.range.startIndex = i;
index = i;
}
else {
this.preOverflowHeight += this.heightLookup[this.itemName];
}
this.range.extendedStartIndex = i;
this.adjustedStartIndex = i;
}
}
if (itemsToBatch.length === batchSize || i === 0) {
for (let j = 0; j < itemsToBatch.length; j++) {
const batchObj = itemsToBatch[j];
if (batchObj.item === null) {
continue;
}
const name = batchObj.name;
const ind = batchObj.index;
const lookupHeight = batchObj.item.offsetHeight;
const oldHeight = this.heightLookup[name];
this.heightLookup[name] = lookupHeight;
batchObj.item.lastHeight = lookupHeight;
this.addResizeSensor(batchObj.item, batchObj.index);
if (oldHeight) {
this.updateEstimatedHeightFromResize(oldHeight, lookupHeight);
}
else {
this.updateEstimatedHeight(lookupHeight);
}
heightCount += lookupHeight;
if (heightCount > this.containerHeight) {
this.preItemOverflowCount++;
if (this.preItemOverflowCount === 0) {
overflow = heightCount - this.containerHeight;
this.range.startIndex = batchObj.index;
index = batchObj.index;
}
else {
this.preOverflowHeight += lookupHeight;
}
this.range.extendedStartIndex = batchObj.index;
this.adjustedStartIndex = batchObj.index;
}
}
batchSize = this.getEstimatedChildInsertions(this.containerHeight - this.lastHeight) + Number(this.preItemOverflow);
if (batchSize <= 0) {
batchSize = Number(this.preItemOverflow);
}
itemsToBatch = [];
}
if (this.preItemOverflowCount >= Number(this.preItemOverflow)) {
break;
}
}
}
return overflow;
}
scrollToExact(index, overflow = 0) {
if (!this.items || this.items.length === 0) {
return;
}
this.resetLastHeight();
index = this.maintainIndexInBounds(index);
overflow = index === 0 && overflow < 0 ? 0 : overflow;
this.adjustedStartIndex = index - Number(this.preItemOverflow) <= 0 ? 0 : index - Number(this.preItemOverflow);
this.preItemOverflowCount = -1;
this.postItemOverflowCount = -1;
this.lastVisibleItemOverflow = 0;
this.range.endIndex = 0;
this.range.extendedEndIndex = 0;
this.removePreScrollItems(this.lastRange.extendedStartIndex, Math.min(this.adjustedStartIndex, this.lastRange.extendedEndIndex));
this.addScrollItems(index, overflow);
this.removePostScrollItems(this.lastRange.extendedEndIndex - 1, Math.max(this.lastRange.extendedStartIndex, this.range.extendedEndIndex));
if (!this.forcedEndIndex) {
overflow = this.addMissingPostScrollItemsAndUpdateOverflow(index, overflow);
}
this.setLastRangeToCurrentRange();
this.setScrollSpacers();
this.lastScrollTop = this.preOverflowHeight + overflow + this.estimatedPreListHeight;
this.listContent.scrollTop = this.lastScrollTop;
this.currentScrollTop = this.lastScrollTop;
this.scrollChangeByFirstIndexedItem = overflow;
this.scrollDispatcherService.dispatchRangeUpdateEvents(this.rangeUpdated, this.range, this, this.elementRef.nativeElement);
this.viewportHasScrolled = true;
}
getRangeChange(scrollChange) {
let heightCount = 0;
let rangeStartCount = 0;
let overflow = 0;
const newRange = { startIndex: null, endIndex: null, extendedStartIndex: null, extendedEndIndex: null };
let itemName;
if (scrollChange > 0) {
for (let i = this.range.startIndex; i <= this.range.endIndex + Number(this.itemLoadLimit); i++) {
overflow = scrollChange - heightCount;
itemName = 'item' + i;
if (this.heightLookup[itemName]) {
heightCount += this.heightLookup[itemName];
}
else {
heightCount += this.avgItemHeight;
}
if (heightCount >= scrollChange) {
break;
}
rangeStartCount++;
}
newRange.startIndex = this.range.startIndex + rangeStartCount;
newRange.endIndex = rangeStartCount < this.range.endIndex - this.range.startIndex ? this.range.endIndex : newRange.startIndex + 1;
}
if (scrollChange < 0) {
rangeStartCount = -1;
overflow = scrollChange;
for (let i = this.range.startIndex - 1; i >= 0; i--) {
itemName = 'item' + i;
if (this.heightLookup[itemName]) {
overflow += this.heightLookup[itemName];
heightCount += this.heightLookup[itemName];
}
else {
overflow += this.avgItemHeight;
heightCount += this.avgItemHeight;
}
if (overflow >= 0) {
break;
}
rangeStartCount--;
}
newRange.startIndex = this.range.startIndex + rangeStartCount >= 0 ? this.range.startIndex + rangeStartCount : 0;
newRange.endIndex = rangeStartCount < this.range.endIndex - this.range.startIndex ? this.range.endIndex : newRange.startIndex + 1;
}
this.scrollChangeByFirstIndexedItem = overflow;
return newRange;
}
refreshViewport(recalculateRows = false) {
if (recalculateRows) {
for (let i = this.range.extendedStartIndex; i < this.range.extendedEndIndex; i++) {
this.recalculateRowHeight(i);
}
}
this.scrollToExact(this.range.startIndex, this.scrollChangeByFirstIndexedItem);
}
updateScrollFromRange(newRange) {
if (newRange.startIndex !== null) {
if (this.range.startIndex !== newRange.startIndex || this.lastVisibleItemOverflow < 0) {
this.range.startIndex = newRange.startIndex;
this.range.endIndex = newRange.endIndex;
this.refreshViewport();
}
else {
this.lastScrollTop = this.currentScrollTop;
}
}
this.lastScrollTop = this.currentScrollTop;
}
initScroll(options) {
this.items = options.items;
this._cloneMethod = options.generateCloneMethod;
const itemsAreEmpty = this.items.length === 0;
let index = options.initialIndex ? options.initialIndex : 0;
if (this.virtualNexus && this.virtualNexus.virtualForDirective._template) {
clearTimeout(this.timeoutID);
this.timeoutID = setTimeout(() => {
this.cloneFromTemplateRef = true;
this.verifyViewportIsReady();
this.initFirstScroll(index);
});
}
else {
this.template = document.getElementById(this.templateID);
this.verifyViewportIsReady();
this.initFirstScroll(index);
}
}
verifyViewportIsReady() {
if (this.templateID === '' && !this.templateIsSet()) {
throw Error('Scroll viewport template ID is not set.');
}
if (!this.itemsAreSet()) {
throw new Error('Scroll viewport requires an array of items. Please supply an items array.');
}
if (!this.cloneMethodIsSet() && !this.templateIsSet()) {
throw new Error('Scroll viewport requires a cloning method or a template. Please supply a method as follows:\n\n (template: HTMLElement, items: any[], index: number) => Node\n\n or supply a tablejsVirtualFor');
}
}
initFirstScroll(index) {
const itemsAreEmpty = this.items.length === 0;
this.refreshContainerHeight();
if (itemsAreEmpty) {
this.items.push(this.placeholderObject);
this.scrollToExact(index, 0);
const node = this.virtualNexus.virtualForDirective._viewContainer.get(1).rootNodes[0];
this.renderer.setStyle(node, 'height', '0px');
this.renderer.setStyle(node, 'minHeight', '0px');
this.renderer.setStyle(node, 'overflow', 'hidden');
}
else {
this.scrollToExact(index, 0);
}
this.scrollDispatcherService.dispatchViewportInitializedEvents(this.viewportInitialized, this, this.elementRef.nativeElement);
}
itemsAreSet() {
return !!this.items;
}
cloneMethodIsSet() {
return !!this._cloneMethod;
}
templateIsSet() {
return this.virtualNexus.virtualForDirective._template !== undefined && this.virtualNexus.virtualForDirective._template !== null;
}
}
ScrollViewportDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.3", ngImport: i0, type: ScrollViewportDirective, deps: [{ token: i0.ElementRef }, { token: i1.GridService }, { token: DOCUMENT }, { token: i2.DirectiveRegistrationService }, { token: i3.ScrollDispatcherService }, { token: i4.OperatingSystemService }, { token: i0.ChangeDetectorRef }, { token: i0.RendererFactory2 }], target: i0.ɵɵFactoryTarget.Directive });
ScrollViewportDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.0.3", type: Sc