Skip to content

Commit 7f41efa

Browse files
committed
fix(repeat): add sizing handler
1 parent 3c62cfc commit 7f41efa

File tree

6 files changed

+226
-178
lines changed

6 files changed

+226
-178
lines changed

src/array-virtual-repeat-strategy.ts

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ICollectionObserverSplice, mergeSplice } from 'aurelia-binding';
22
import { ViewSlot } from 'aurelia-templating';
33
import { ArrayRepeatStrategy, createFullOverrideContext } from 'aurelia-templating-resources';
4-
import { IView, IVirtualRepeatStrategy } from './interfaces';
4+
import { IView, IVirtualRepeatStrategy, VirtualizationCalculation } from './interfaces';
55
import {
66
Math$abs,
77
Math$floor,
@@ -22,12 +22,12 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
2222
return repeat.addView(overrideContext.bindingContext, overrideContext);
2323
}
2424

25-
initCalculation(repeat: VirtualRepeat, items: any[]): boolean {
25+
initCalculation(repeat: VirtualRepeat, items: any[]): VirtualizationCalculation {
2626
const itemCount = items.length;
2727
// when there is no item, bails immediately
2828
// and return false to notify calculation finished unsuccessfully
2929
if (!(itemCount > 0)) {
30-
return false;
30+
return VirtualizationCalculation.reset;
3131
}
3232
// before invoking instance changed, there needs to be basic calculation on how
3333
// the required vairables such as item height and elements required
@@ -38,19 +38,21 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
3838
}
3939
const isFixedHeightContainer = repeat._fixedHeightContainer = hasOverflowScroll(containerEl);
4040
const firstView = repeat._firstView();
41-
const itemHeight = repeat.itemHeight = calcOuterHeight(firstView.firstChild as Element);
41+
const itemHeight = calcOuterHeight(firstView.firstChild as Element);
4242
// when item height is 0, bails immediately
4343
// and return false to notify calculation has finished unsuccessfully
4444
// it cannot be processed further when item is 0
4545
if (itemHeight === 0) {
46-
return false;
46+
return VirtualizationCalculation.none;
4747
}
48+
repeat.itemHeight = itemHeight;
4849
const scroll_el_height = isFixedHeightContainer
4950
? calcScrollHeight(containerEl)
5051
: document.documentElement.clientHeight;
52+
// console.log({ scroll_el_height })
5153
const elementsInView = repeat.elementsInView = Math$floor(scroll_el_height / itemHeight) + 1;
5254
const viewsCount = repeat._viewsLength = elementsInView * 2;
53-
return true;
55+
return VirtualizationCalculation.has_sizing | VirtualizationCalculation.observe_scroller;
5456
}
5557

5658
/**
@@ -88,8 +90,7 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
8890
if (currItemCount === 0) {
8991
repeat.removeAllViews(/*return to cache?*/true, /*skip animation?*/false);
9092
repeat._resetCalculation();
91-
delete repeat.__queuedSplices;
92-
delete repeat.__array;
93+
repeat.__queuedSplices = repeat.__array = undefined;
9394
return false;
9495
}
9596
/*
@@ -99,26 +100,32 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
99100
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
100101
That "first" is calculated and passed into here
101102
*/
102-
// remove unneeded views.
103+
104+
// if the number of items shrinks to less than number of active views
105+
// remove all unneeded views
103106
let realViewsCount = repeat.viewCount();
104-
// console.log('0. Start inplace process item', { realViewsCount, currItemCount, els: repeat.elementsInView, max: repeat._viewsLength })
105107
while (realViewsCount > currItemCount) {
106108
realViewsCount--;
107109
repeat.removeView(realViewsCount, /*return to cache?*/true, /*skip animation?*/false);
108110
}
109-
// console.log(realViewsCount, repeat._viewsLength)
111+
// there is situation when container height shrinks
112+
// the real views count will be greater than new maximum required view count
113+
// remove all unnecessary view
114+
while (realViewsCount > repeat._viewsLength) {
115+
realViewsCount--;
116+
repeat.removeView(realViewsCount, /*return to cache?*/true, /*skip animation?*/false);
117+
}
110118
realViewsCount = Math$min(realViewsCount, repeat._viewsLength);
119+
111120
const local = repeat.local;
112121
const lastIndex = currItemCount - 1;
113122

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

123130
repeat._first = firstIndex;
124131
// re-evaluate bindings on existing views.
@@ -151,9 +158,6 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
151158
}
152159
// add new views
153160
const minLength = Math$min(repeat._viewsLength, currItemCount);
154-
// console.log('4. After updating existing views',
155-
// { firstIndex, minLength, maxView: repeat._viewsLength, currItemCount, realViewsCount }
156-
// )
157161
for (let i = realViewsCount; i < minLength; i++) {
158162
const overrideContext = createFullOverrideContext(repeat, items[i], i, currItemCount);
159163
repeat.addView(overrideContext.bindingContext, overrideContext);
@@ -165,7 +169,7 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
165169
_standardProcessInstanceMutated(repeat: VirtualRepeat, array: Array<any>, splices: ICollectionObserverSplice[]): void {
166170
if (repeat.__queuedSplices) {
167171
for (let i = 0, ii = splices.length; i < ii; ++i) {
168-
let {index, removed, addedCount} = splices[i];
172+
const { index, removed, addedCount } = splices[i];
169173
mergeSplice(repeat.__queuedSplices, index, removed, addedCount);
170174
}
171175
repeat.__array = array.slice(0);
@@ -174,23 +178,21 @@ export class ArrayVirtualRepeatStrategy extends ArrayRepeatStrategy implements I
174178
if (array.length === 0) {
175179
repeat.removeAllViews(/*return to cache?*/true, /*skip animation?*/false);
176180
repeat._resetCalculation();
177-
delete repeat.__queuedSplices;
178-
delete repeat.__array;
181+
repeat.__queuedSplices = repeat.__array = undefined;
179182
return;
180183
}
181184

182-
let maybePromise = this._runSplices(repeat, array.slice(0), splices);
185+
const maybePromise = this._runSplices(repeat, array.slice(0), splices);
183186
if (maybePromise instanceof Promise) {
184-
let queuedSplices = repeat.__queuedSplices = [];
187+
const queuedSplices = repeat.__queuedSplices = [];
185188

186-
let runQueuedSplices = () => {
189+
const runQueuedSplices = () => {
187190
if (! queuedSplices.length) {
188-
delete repeat.__queuedSplices;
189-
delete repeat.__array;
191+
repeat.__queuedSplices = repeat.__array = undefined;
190192
return;
191193
}
192194

193-
let nextPromise = this._runSplices(repeat, repeat.__array, queuedSplices) || Promise.resolve();
195+
const nextPromise = this._runSplices(repeat, repeat.__array, queuedSplices) || Promise.resolve();
194196
nextPromise.then(runQueuedSplices);
195197
};
196198

src/aurelia-ui-virtualization.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ export {
1414
};
1515

1616
export {
17-
IScrollNextScrollContext
17+
IScrollNextScrollContext,
18+
VirtualizationEvents
1819
} from './interfaces';

src/interfaces.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export interface IVirtualRepeatStrategy extends RepeatStrategy {
5555
*
5656
* @returns `false` to notify that calculation hasn't been finished
5757
*/
58-
initCalculation(repeat: VirtualRepeat, items: number | any[] | Map<any, any> | Set<any>): boolean;
58+
initCalculation(repeat: VirtualRepeat, items: number | any[] | Map<any, any> | Set<any>): VirtualizationCalculation;
5959

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

154+
export const enum VirtualizationCalculation {
155+
none = 0b0_00000,
156+
reset = 0b0_00001,
157+
has_sizing = 0b0_00010,
158+
observe_scroller = 0b0_00100
159+
}
160+
161+
/**
162+
* List of events that can be used to notify virtual repeat that size has changed
163+
*/
164+
export const VirtualizationEvents = Object.assign(Object.create(null), {
165+
scrollerSizeChange: 'virtual-repeat-scroller-size-changed' as 'virtual-repeat-scroller-size-changed',
166+
itemSizeChange: 'virtual-repeat-item-size-changed' as 'virtual-repeat-item-size-changed'
167+
}) as {
168+
scrollerSizeChange: 'virtual-repeat-scroller-size-changed';
169+
itemSizeChange: 'virtual-repeat-item-size-changed';
170+
};
171+
154172
// export const enum IVirtualRepeatState {
155173
// isAtTop = 0b0_000000_000,
156174
// isLastIndex = 0b0_000000_000,

src/null-virtual-repeat-strategy.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { NullRepeatStrategy, RepeatStrategy } from 'aurelia-templating-resources';
22
import { VirtualRepeat } from './virtual-repeat';
3-
import { IVirtualRepeatStrategy, IView } from './interfaces';
3+
import { IVirtualRepeatStrategy, IView, VirtualizationCalculation } from './interfaces';
44

55
export class NullVirtualRepeatStrategy extends NullRepeatStrategy implements IVirtualRepeatStrategy {
66

7-
initCalculation(repeat: VirtualRepeat, items: any) {
8-
repeat.elementsInView
9-
= repeat.itemHeight
7+
initCalculation(repeat: VirtualRepeat, items: any): VirtualizationCalculation {
8+
repeat.itemHeight
9+
= repeat.elementsInView
1010
= repeat._viewsLength = 0;
1111
// null/undefined virtual repeat strategy does not require any calculation
12-
// returning true to signal that
13-
return true;
12+
// returning has_sizing to signal that
13+
return VirtualizationCalculation.has_sizing;
1414
}
1515

1616
// a violation of base contract, won't work in strict mode

src/utilities-dom.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ export const insertBeforeNode = (view: IView, bottomBuffer: Element): void => {
7575
* There are steps in the middle to account for offsetParent but it's basically that
7676
*/
7777
export const getDistanceToParent = (child: HTMLElement, parent: HTMLElement) => {
78+
// optimizable case where child is the first child of parent
79+
// and parent is the target parent to calculate
80+
if (child.previousSibling === null && child.parentNode === parent) {
81+
return 0;
82+
}
7883
const offsetParent = child.offsetParent as HTMLElement;
7984
const childOffsetTop = child.offsetTop;
8085
// [el] <-- offset parent === parent

0 commit comments

Comments
 (0)