terrier-engine 4.26.4 → 4.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {PartPlugin} from "tuff-core/plugins"
|
|
2
|
+
import {Logger} from "tuff-core/logging"
|
|
3
|
+
import {Part, PartConstructor} from "tuff-core/parts"
|
|
4
|
+
|
|
5
|
+
const log = new Logger('LoadOnScrollPlugin')
|
|
6
|
+
|
|
7
|
+
export type LoadOnScrollOptions<TState> = {
|
|
8
|
+
// The name of the collection to load on scroll
|
|
9
|
+
collectionName: string
|
|
10
|
+
// The type of parts in the collection
|
|
11
|
+
collectionPartType: PartConstructor<Part<TState>, TState>
|
|
12
|
+
// Called to load the next state. If undefined is returned, no more states will be loaded
|
|
13
|
+
loadNextStates: (existingStates: TState[]) => Promise<TState[] | undefined>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Given a collection name, loads more elements into the collection as the user continues to scroll.
|
|
18
|
+
* When the last item of the collection is in view, it will begin to load the next element.
|
|
19
|
+
*/
|
|
20
|
+
export default class LoadOnScrollPlugin<TState> extends PartPlugin<LoadOnScrollOptions<TState>> {
|
|
21
|
+
private observer?: IntersectionObserver
|
|
22
|
+
|
|
23
|
+
update(elem: HTMLElement) {
|
|
24
|
+
super.update(elem)
|
|
25
|
+
|
|
26
|
+
const collectionParts = this.part.getCollectionParts(this.state.collectionName)
|
|
27
|
+
|
|
28
|
+
const lastPart = collectionParts[collectionParts.length - 1]
|
|
29
|
+
if (!lastPart) return
|
|
30
|
+
|
|
31
|
+
const lastElement = elem.querySelector(`#${lastPart.id}`)
|
|
32
|
+
if (!lastElement) {
|
|
33
|
+
log.warn(`No element for last part in collection ${this.state.collectionName}`, lastPart)
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const collectionContainer = this.part.getCollectionContainer(this.state.collectionName)
|
|
38
|
+
if (this.observer == undefined || this.observer.root != collectionContainer) {
|
|
39
|
+
// The old observer is referencing an old collection container so we need to create a new one
|
|
40
|
+
this.observer = new IntersectionObserver(this.onIntersect.bind(this), {
|
|
41
|
+
root: collectionContainer,
|
|
42
|
+
threshold: 0.25,
|
|
43
|
+
})
|
|
44
|
+
} else {
|
|
45
|
+
this.observer.disconnect()
|
|
46
|
+
}
|
|
47
|
+
this.observer.observe(lastElement)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private onIntersect(entries: IntersectionObserverEntry[], obs: IntersectionObserver) {
|
|
51
|
+
if (entries.length && entries[0].isIntersecting) {
|
|
52
|
+
obs.unobserve(entries[0].target)
|
|
53
|
+
this.loadNextState().then()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private async loadNextState() {
|
|
58
|
+
const partStates = this.part.getCollectionParts(this.state.collectionName).map(p => p.state) as TState[]
|
|
59
|
+
const nextState = await this.state.loadNextStates(partStates)
|
|
60
|
+
if (nextState === undefined) {
|
|
61
|
+
// No more states to load; remove the plugin to avoid additional loads
|
|
62
|
+
this.observer?.disconnect()
|
|
63
|
+
this.observer = undefined
|
|
64
|
+
this.part.removePlugin(this.id)
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
partStates.push(...nextState)
|
|
68
|
+
this.part.assignCollection(this.state.collectionName, this.state.collectionPartType, partStates)
|
|
69
|
+
this.part.stale()
|
|
70
|
+
}
|
|
71
|
+
}
|