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
@@ -4,7 +4,7 @@
4
4
  "files": [
5
5
  "*"
6
6
  ],
7
- "version": "4.26.4",
7
+ "version": "4.29.0",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Terrier-Tech/terrier-engine"
@@ -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
+ }