Skip to content

Commit 7d5f68d

Browse files
authored
Merge pull request #47 from yappbox/enhancement/coll-scroll-v-header
Add header support to <CollectionScrollView>
2 parents e553715 + c490fd9 commit 7d5f68d

File tree

15 files changed

+1548
-145
lines changed

15 files changed

+1548
-145
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{yield this.renderCells}}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import { A } from '@ember/array';
2+
import Component from '@glimmer/component';
3+
import identity from 'ember-collection/utils/identity';
4+
import { tracked } from '@glimmer/tracking';
5+
import { reads } from 'macro-decorators';
6+
7+
class Cell {
8+
@tracked hidden;
9+
@tracked item;
10+
@tracked index;
11+
@tracked style;
12+
13+
constructor(key, item, index, style) {
14+
this.key = key;
15+
this.hidden = false;
16+
this.item = item;
17+
this.index = index;
18+
this.style = style;
19+
}
20+
}
21+
22+
function noop() {}
23+
24+
export default class CollectionScrollViewCollectionItems extends Component {
25+
_contentSize;
26+
cells = A();
27+
cellMap = Object.create(null);
28+
29+
@reads('args.buffer', 5) buffer;
30+
@reads('args.scrollLeft', 0) scrollLeft;
31+
@reads('args.scrollTop', 0) scrollTop;
32+
@reads('args.estimatedSize', { width: 0, height: 0 }) estimatedSize;
33+
@reads('args.cellLayout') cellLayout;
34+
35+
get clientWidth() {
36+
let { clientSize, estimatedSize } = this.args;
37+
return clientSize ? clientSize.width : estimatedSize.width;
38+
}
39+
40+
get clientHeight() {
41+
let { clientSize, estimatedSize } = this.args;
42+
return clientSize ? clientSize.height : estimatedSize.height;
43+
}
44+
45+
safeRerender(){
46+
if (this.isDestroyed || this.isDestroying) {return;}
47+
this.rerender();
48+
}
49+
50+
willDestroyElement() {
51+
let { items } = this;
52+
if (items && items.removeArrayObserver) {
53+
items.removeArrayObserver(this, {
54+
willChange: noop,
55+
didChange: 'safeRerender'
56+
});
57+
}
58+
}
59+
60+
get items(){
61+
let rawItems = this.args.items;
62+
63+
if (this._rawItems !== rawItems) {
64+
this._rawItems = rawItems;
65+
if (this._items && this._items.removeArrayObserver) {
66+
this._items.removeArrayObserver(this, {
67+
willChange: noop,
68+
didChange: 'safeRerender'
69+
});
70+
}
71+
let items = A(rawItems);
72+
this._items = items;
73+
74+
if (items && items.addArrayObserver) {
75+
items.addArrayObserver(this, {
76+
willChange: noop,
77+
didChange: 'safeRerender'
78+
});
79+
}
80+
return items;
81+
}
82+
return this._items;
83+
}
84+
85+
get contentSize() {
86+
this._contentSize = this._contentSize || this.cellLayout.contentSize(this.clientWidth, this.clientHeight);
87+
return this._contentSize;
88+
}
89+
90+
set contentSize(contentSize) {
91+
if (this._contentSize !== contentSize) {
92+
let { cellLayout, clientWidth, clientHeight } = this;
93+
this._contentSize = cellLayout.contentSize(clientWidth, clientHeight);
94+
if (this.args.onContentSizeUpdated) {
95+
this.args.onContentSizeUpdated(contentSize);
96+
}
97+
}
98+
}
99+
100+
get renderCells() {
101+
let { cellLayout, cells, items, scrollLeft, scrollTop, clientWidth, clientHeight } = this;
102+
if (!items) { return []; }
103+
const numItems = items.length;
104+
if (cellLayout.length !== numItems) {
105+
cellLayout.length = numItems;
106+
}
107+
108+
let priorMap = this.cellMap;
109+
let cellMap = Object.create(null);
110+
111+
let index = cellLayout.indexAt(scrollLeft, scrollTop, clientWidth, clientHeight);
112+
let count = cellLayout.count(scrollLeft, scrollTop, clientWidth, clientHeight);
113+
let bufferBefore = Math.min(index, this.buffer);
114+
index -= bufferBefore;
115+
count += bufferBefore;
116+
count = Math.min(count + this.buffer, items.length - index);
117+
let i, style, itemIndex, itemKey, cell;
118+
119+
let newItems = [];
120+
121+
for (i=0; i<count; i++) {
122+
itemIndex = index+i;
123+
itemKey = identity(items.objectAt(itemIndex));
124+
if (priorMap) {
125+
cell = priorMap[itemKey];
126+
}
127+
if (cell) {
128+
style = cellLayout.formatItemStyle(itemIndex, clientWidth, clientHeight);
129+
cell.style = style;
130+
cell.hidden = false;
131+
cell.key = itemKey;
132+
cell.index = itemIndex;
133+
cellMap[itemKey] = cell;
134+
} else {
135+
newItems.push(itemIndex);
136+
}
137+
}
138+
139+
for (i=0; i < cells.length; i++) {
140+
cell = cells[i];
141+
if (!cellMap[cell.key]) {
142+
if (newItems.length) {
143+
itemIndex = newItems.pop();
144+
let item = items.objectAt(itemIndex);
145+
itemKey = identity(item);
146+
style = cellLayout.formatItemStyle(itemIndex, clientWidth, clientHeight);
147+
cell.style = style;
148+
cell.key = itemKey;
149+
cell.index = itemIndex;
150+
cell.item = item;
151+
cell.hidden = false;
152+
cellMap[itemKey] = cell;
153+
} else {
154+
cell.hidden = true;
155+
cell.style = 'height: 0; display: none;';
156+
}
157+
}
158+
}
159+
160+
for (i = 0; i < newItems.length; i++) {
161+
itemIndex = newItems[i];
162+
let item = items.objectAt(itemIndex);
163+
itemKey = identity(item);
164+
style = cellLayout.formatItemStyle(itemIndex, clientWidth, clientHeight);
165+
cell = new Cell(itemKey, item, itemIndex, style);
166+
cellMap[itemKey] = cell;
167+
cells.pushObject(cell);
168+
}
169+
this.cellMap = cellMap;
170+
this.contentSize = cellLayout.contentSize(clientWidth, clientHeight);
171+
return cells;
172+
}
173+
}

addon/components/collection-scroll-view/component.js

Lines changed: 0 additions & 92 deletions
This file was deleted.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<ScrollView
2+
class="CollectionScrollView"
3+
@contentHeight={{this.scrollHeight}}
4+
@scrollTopOffset={{this.scrollTopOffset}}
5+
@initialScrollTop={{@initialScrollTop}}
6+
@key={{@key}}
7+
@auxiliaryComponent={{if @auxiliaryComponent
8+
(component @auxiliaryComponent
9+
cell-layout=this.cellLayout
10+
items=@items
11+
estimated-width=@estimated-width
12+
estimated-height=@estimated-height
13+
client-width=this.clientWidth
14+
client-height=this.clientHeight
15+
scroll-top=this.scrollTop
16+
)
17+
}}
18+
@clientSizeChange={{this.clientSizeChange}}
19+
@scrollChange={{this.scrollChange}}
20+
@scrolledToTopChange={{this.onScrolledToTopChange}}
21+
{{ref this 'element'}}
22+
...attributes
23+
as |scrollViewApi|
24+
>
25+
26+
{{#if (has-block 'header')}}
27+
<div {{on-resize (fn this.updateHeaderDimensions scrollViewApi)}}>
28+
{{yield to="header"}}
29+
</div>
30+
{{/if}}
31+
32+
{{#unless (and (has-block 'header') (is-empty this.headerDimensions))}}
33+
<div data-test-collection-items-container style={{html-safe (concat "position:relative;height:" this.contentSize.height "px;width:" this.contentSize.width "px")}}>
34+
<CollectionScrollView::CollectionItems
35+
@clientSize={{this.collectionClientSize}}
36+
@scrollTop={{this.collectionScrollTop}}
37+
@estimatedSize={{hash width=@estimated-width height=@estimated-height}}
38+
@items={{@items}}
39+
@buffer={{@buffer}}
40+
@cellLayout={{this.cellLayout}}
41+
@onContentSizeUpdated={{this.updateContentSizeAfterRender}}
42+
as |cells|
43+
>
44+
{{~#each cells as |cell|~}}
45+
<div style={{html-safe cell.style}}>{{yield cell.item cell.index scrollViewApi to="row"}}</div>
46+
{{~/each~}}
47+
</CollectionScrollView::CollectionItems>
48+
</div>
49+
{{/unless}}
50+
51+
{{#if @revealService}}
52+
{{emitter-action
53+
emitter=@revealService
54+
eventName="revealItemById"
55+
action=(fn this.scrollToItem scrollViewApi)
56+
}}
57+
{{/if}}
58+
</ScrollView>

0 commit comments

Comments
 (0)