Skip to content
Permalink
Browse files

fix(repeat): add sizing handler

  • Loading branch information...
bigopon committed Mar 19, 2019
1 parent 3c62cfc commit 7f41efaa06115128b89394e0648290533c282a4e
@@ -1,7 +1,7 @@
import { ICollectionObserverSplice, mergeSplice } from 'aurelia-binding';
import { ViewSlot } from 'aurelia-templating';
import { ArrayRepeatStrategy, createFullOverrideContext } from 'aurelia-templating-resources';
import { IView, IVirtualRepeatStrategy } from './interfaces';
import { IView, IVirtualRepeatStrategy, VirtualizationCalculation } from './interfaces';
import {
Math$abs,
Math$floor,
@@ -22,12 +22,12 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
return repeat.addView(overrideContext.bindingContext, overrideContext);
}

initCalculation(repeat: VirtualRepeat, items: any[]): boolean {
initCalculation(repeat: VirtualRepeat, items: any[]): VirtualizationCalculation {
const itemCount = items.length;
// when there is no item, bails immediately
// and return false to notify calculation finished unsuccessfully
if (!(itemCount > 0)) {
return false;
return VirtualizationCalculation.reset;
}
// before invoking instance changed, there needs to be basic calculation on how
// the required vairables such as item height and elements required
@@ -38,19 +38,21 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
}
const isFixedHeightContainer = repeat._fixedHeightContainer = hasOverflowScroll(containerEl);
const firstView = repeat._firstView();
const itemHeight = repeat.itemHeight = calcOuterHeight(firstView.firstChild as Element);
const itemHeight = calcOuterHeight(firstView.firstChild as Element);
// when item height is 0, bails immediately
// and return false to notify calculation has finished unsuccessfully
// it cannot be processed further when item is 0
if (itemHeight === 0) {
return false;
return VirtualizationCalculation.none;
}
repeat.itemHeight = itemHeight;
const scroll_el_height = isFixedHeightContainer
? calcScrollHeight(containerEl)
: document.documentElement.clientHeight;
// console.log({ scroll_el_height })
const elementsInView = repeat.elementsInView = Math$floor(scroll_el_height / itemHeight) + 1;
const viewsCount = repeat._viewsLength = elementsInView * 2;
return true;
return VirtualizationCalculation.has_sizing | VirtualizationCalculation.observe_scroller;
}

/**
@@ -88,8 +90,7 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
if (currItemCount === 0) {
repeat.removeAllViews(/*return to cache?*/true, /*skip animation?*/false);
repeat._resetCalculation();
delete repeat.__queuedSplices;
delete repeat.__array;
repeat.__queuedSplices = repeat.__array = undefined;
return false;
}
/*
@@ -99,26 +100,32 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
To figure out that one, we're going to have to know where we are in our scrolling so we can know how far down we've gone to show the first view
That "first" is calculated and passed into here
*/
// remove unneeded views.

// if the number of items shrinks to less than number of active views
// remove all unneeded views
let realViewsCount = repeat.viewCount();
// console.log('0. Start inplace process item', { realViewsCount, currItemCount, els: repeat.elementsInView, max: repeat._viewsLength })
while (realViewsCount > currItemCount) {
realViewsCount--;
repeat.removeView(realViewsCount, /*return to cache?*/true, /*skip animation?*/false);
}
// console.log(realViewsCount, repeat._viewsLength)
// there is situation when container height shrinks
// the real views count will be greater than new maximum required view count
// remove all unnecessary view
while (realViewsCount > repeat._viewsLength) {
realViewsCount--;
repeat.removeView(realViewsCount, /*return to cache?*/true, /*skip animation?*/false);
}
realViewsCount = Math$min(realViewsCount, repeat._viewsLength);

const local = repeat.local;
const lastIndex = currItemCount - 1;

// console.log('1. firstIndex, realCount, lastIndex', { firstIndex, realViewsCount, lastIndex })
if (firstIndex + realViewsCount > lastIndex) {
// first = currItemCount - realViewsCount instead of: first = currItemCount - 1 - realViewsCount;
// this is because during view update
// view(i) starts at 0 and ends at less than last
firstIndex = Math$max(0, currItemCount - realViewsCount);
}
// console.log('2. firstIndex, realCount, lastIndex', { firstIndex, realViewsCount, lastIndex })

repeat._first = firstIndex;
// re-evaluate bindings on existing views.
@@ -151,9 +158,6 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
}
// add new views
const minLength = Math$min(repeat._viewsLength, currItemCount);
// console.log('4. After updating existing views',
// { firstIndex, minLength, maxView: repeat._viewsLength, currItemCount, realViewsCount }
// )
for (let i = realViewsCount; i < minLength; i++) {
const overrideContext = createFullOverrideContext(repeat, items[i], i, currItemCount);
repeat.addView(overrideContext.bindingContext, overrideContext);
@@ -165,7 +169,7 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
_standardProcessInstanceMutated(repeat: VirtualRepeat, array: Array<any>, splices: ICollectionObserverSplice[]): void {
if (repeat.__queuedSplices) {
for (let i = 0, ii = splices.length; i < ii; ++i) {
let {index, removed, addedCount} = splices[i];
const { index, removed, addedCount } = splices[i];
mergeSplice(repeat.__queuedSplices, index, removed, addedCount);
}
repeat.__array = array.slice(0);
@@ -174,23 +178,21 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
if (array.length === 0) {
repeat.removeAllViews(/*return to cache?*/true, /*skip animation?*/false);
repeat._resetCalculation();
delete repeat.__queuedSplices;
delete repeat.__array;
repeat.__queuedSplices = repeat.__array = undefined;
return;
}

let maybePromise = this._runSplices(repeat, array.slice(0), splices);
const maybePromise = this._runSplices(repeat, array.slice(0), splices);
if (maybePromise instanceof Promise) {
let queuedSplices = repeat.__queuedSplices = [];
const queuedSplices = repeat.__queuedSplices = [];

let runQueuedSplices = () => {
const runQueuedSplices = () => {
if (! queuedSplices.length) {
delete repeat.__queuedSplices;
delete repeat.__array;
repeat.__queuedSplices = repeat.__array = undefined;
return;
}

let nextPromise = this._runSplices(repeat, repeat.__array, queuedSplices) || Promise.resolve();
const nextPromise = this._runSplices(repeat, repeat.__array, queuedSplices) || Promise.resolve();
nextPromise.then(runQueuedSplices);
};

@@ -14,5 +14,6 @@ export {
};

export {
IScrollNextScrollContext
IScrollNextScrollContext,
VirtualizationEvents
} from './interfaces';
@@ -55,7 +55,7 @@ export interface IVirtualRepeatStrategy extends RepeatStrategy {
*
* @returns `false` to notify that calculation hasn't been finished
*/
initCalculation(repeat: VirtualRepeat, items: number | any[] | Map<any, any> | Set<any>): boolean;
initCalculation(repeat: VirtualRepeat, items: number | any[] | Map<any, any> | Set<any>): VirtualizationCalculation;

/**
* Get the observer based on collection type of `items`
@@ -151,6 +151,24 @@ export interface IScrollerInfo {
height: number;
}

export const enum VirtualizationCalculation {
none = 0b0_00000,
reset = 0b0_00001,
has_sizing = 0b0_00010,
observe_scroller = 0b0_00100
}

/**
* List of events that can be used to notify virtual repeat that size has changed
*/
export const VirtualizationEvents = Object.assign(Object.create(null), {
scrollerSizeChange: 'virtual-repeat-scroller-size-changed' as 'virtual-repeat-scroller-size-changed',
itemSizeChange: 'virtual-repeat-item-size-changed' as 'virtual-repeat-item-size-changed'
}) as {
scrollerSizeChange: 'virtual-repeat-scroller-size-changed';
itemSizeChange: 'virtual-repeat-item-size-changed';
};

// export const enum IVirtualRepeatState {
// isAtTop = 0b0_000000_000,
// isLastIndex = 0b0_000000_000,
@@ -1,16 +1,16 @@
import { NullRepeatStrategy, RepeatStrategy } from 'aurelia-templating-resources';
import { VirtualRepeat } from './virtual-repeat';
import { IVirtualRepeatStrategy, IView } from './interfaces';
import { IVirtualRepeatStrategy, IView, VirtualizationCalculation } from './interfaces';

export class NullVirtualRepeatStrategy extends NullRepeatStrategy implements IVirtualRepeatStrategy {

initCalculation(repeat: VirtualRepeat, items: any) {
repeat.elementsInView
= repeat.itemHeight
initCalculation(repeat: VirtualRepeat, items: any): VirtualizationCalculation {
repeat.itemHeight
= repeat.elementsInView
= repeat._viewsLength = 0;
// null/undefined virtual repeat strategy does not require any calculation
// returning true to signal that
return true;
// returning has_sizing to signal that
return VirtualizationCalculation.has_sizing;
}

// a violation of base contract, won't work in strict mode
@@ -75,6 +75,11 @@ export const insertBeforeNode = (view: IView, bottomBuffer: Element): void => {
* There are steps in the middle to account for offsetParent but it's basically that
*/
export const getDistanceToParent = (child: HTMLElement, parent: HTMLElement) => {
// optimizable case where child is the first child of parent
// and parent is the target parent to calculate
if (child.previousSibling === null && child.parentNode === parent) {
return 0;
}
const offsetParent = child.offsetParent as HTMLElement;
const childOffsetTop = child.offsetTop;
// [el] <-- offset parent === parent
Oops, something went wrong.

0 comments on commit 7f41efa

Please sign in to comment.
You can’t perform that action at this time.