svelte-infinite 0.5.0 → 0.5.2
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/README.md +8 -9
- package/dist/InfiniteLoader.svelte +53 -74
- package/dist/InfiniteLoader.svelte.d.ts +4 -3
- package/dist/loaderState.svelte.d.ts +0 -1
- package/dist/loaderState.svelte.js +0 -1
- package/package.json +23 -23
package/README.md
CHANGED
|
@@ -12,14 +12,15 @@
|
|
|
12
12
|
[](https://npmjs.org/packages/svelte-infinite)
|
|
13
13
|
[](https://svelte-5-infinite.vercel.app)
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
✨ Flexible
|
|
16
|
+
⏰ Infinite Loop Detection
|
|
17
|
+
📣 Control Loader State
|
|
18
|
+
🔎 `IntersectionObserver` based
|
|
19
|
+
🔥 Using Runes and Snippets
|
|
20
|
+
🧑🔧 **Demo**: [svelte-5-infinite.vercel.app](https://svelte-5-infinite.vercel.app)
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
📣 Control Loader State
|
|
20
|
-
🔎 `IntersectionObserver` based
|
|
21
|
-
🔥 Using Runes and Snippets
|
|
22
|
-
🧑🔧 **Demo**: [svelte-5-infinite.vercel.app](https://svelte-5-infinite.vercel.app)
|
|
22
|
+
> [!WARNING]
|
|
23
|
+
> `v0.5.0` contains a breaking change. See [this PR](https://github.com/ndom91/svelte-infinite/pull/12) for more details including migration steps.
|
|
23
24
|
|
|
24
25
|
## 🏗️ Getting Started
|
|
25
26
|
|
|
@@ -145,8 +146,6 @@ This is a more realistic example use-case which includes a paginated data endpoi
|
|
|
145
146
|
{/snippet}
|
|
146
147
|
</InfiniteLoader>
|
|
147
148
|
</main>
|
|
148
|
-
|
|
149
|
-
</script>
|
|
150
149
|
```
|
|
151
150
|
|
|
152
151
|
## ♾️ Usage
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { type Snippet } from "svelte"
|
|
3
3
|
import { STATUS, LoaderState } from "./loaderState.svelte"
|
|
4
4
|
|
|
5
5
|
type InfiniteLoaderProps = {
|
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
loopTimeout?: number
|
|
8
8
|
loopDetectionTimeout?: number
|
|
9
9
|
loopMaxCalls?: number
|
|
10
|
-
intersectionOptions?:
|
|
10
|
+
intersectionOptions?: IntersectionObserverInit
|
|
11
11
|
loaderState: LoaderState
|
|
12
12
|
children: Snippet
|
|
13
13
|
loading?: Snippet
|
|
14
14
|
noResults?: Snippet
|
|
15
15
|
noData?: Snippet
|
|
16
16
|
coolingOff?: Snippet
|
|
17
|
-
error?: Snippet<[
|
|
17
|
+
error?: Snippet<[() => Promise<void>]>
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const {
|
|
@@ -34,50 +34,16 @@
|
|
|
34
34
|
|
|
35
35
|
const ERROR_INFINITE_LOOP = `Attempted to execute load function ${loopMaxCalls} or more times within a short period. Please wait before trying again..`
|
|
36
36
|
|
|
37
|
-
// Track load counts to avoid infinite loops
|
|
38
|
-
class LoopTracker {
|
|
39
|
-
coolingOff = false
|
|
40
|
-
#coolingOffTimer: number | null = null
|
|
41
|
-
#timer: number | null = null
|
|
42
|
-
#count = 0
|
|
43
|
-
|
|
44
|
-
// On each call, increment the count and reset the timer
|
|
45
|
-
track() {
|
|
46
|
-
this.#count += 1
|
|
47
|
-
|
|
48
|
-
clearTimeout(this.#timer!)
|
|
49
|
-
// Cooldown, after 2s, reset count to 0
|
|
50
|
-
this.#timer = setTimeout(() => {
|
|
51
|
-
this.#count = 0
|
|
52
|
-
}, loopDetectionTimeout)
|
|
53
|
-
|
|
54
|
-
// If count > loopMaxCalls, begin cool-down period
|
|
55
|
-
// and start timer to reset loop count tracker
|
|
56
|
-
if (this.#count >= loopMaxCalls) {
|
|
57
|
-
console.error(ERROR_INFINITE_LOOP)
|
|
58
|
-
|
|
59
|
-
this.coolingOff = true
|
|
60
|
-
this.#coolingOffTimer = setTimeout(() => {
|
|
61
|
-
this.coolingOff = false
|
|
62
|
-
this.#count = 0
|
|
63
|
-
}, loopTimeout)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
destroy() {
|
|
68
|
-
if (this.#timer) {
|
|
69
|
-
clearTimeout(this.#timer)
|
|
70
|
-
}
|
|
71
|
-
if (this.#coolingOffTimer) {
|
|
72
|
-
clearTimeout(this.#coolingOffTimer)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const loopTracker = new LoopTracker()
|
|
78
|
-
|
|
79
37
|
let intersectionTarget = $state<HTMLElement>()
|
|
80
|
-
let
|
|
38
|
+
let loopTracker = $state<{
|
|
39
|
+
coolingOff: boolean
|
|
40
|
+
count: number
|
|
41
|
+
timers: (number | null)[]
|
|
42
|
+
}>({
|
|
43
|
+
coolingOff: false,
|
|
44
|
+
count: 0,
|
|
45
|
+
timers: []
|
|
46
|
+
})
|
|
81
47
|
|
|
82
48
|
let showLoading = $derived(loaderState.status === STATUS.LOADING)
|
|
83
49
|
let showError = $derived(loaderState.status === STATUS.ERROR)
|
|
@@ -85,10 +51,34 @@
|
|
|
85
51
|
let showNoData = $derived(loaderState.status === STATUS.COMPLETE && !loaderState.isFirstLoad)
|
|
86
52
|
let showCoolingOff = $derived(loaderState.status !== STATUS.COMPLETE && loopTracker.coolingOff)
|
|
87
53
|
|
|
54
|
+
function trackLoad() {
|
|
55
|
+
loopTracker.count += 1
|
|
56
|
+
|
|
57
|
+
loopTracker.timers.forEach((timer) => {
|
|
58
|
+
if (timer !== null) clearTimeout(timer)
|
|
59
|
+
})
|
|
60
|
+
loopTracker.timers = []
|
|
61
|
+
|
|
62
|
+
loopTracker.timers.push(
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
loopTracker.count = 0
|
|
65
|
+
}, loopDetectionTimeout)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if (loopTracker.count >= loopMaxCalls) {
|
|
69
|
+
console.error(ERROR_INFINITE_LOOP)
|
|
70
|
+
|
|
71
|
+
loopTracker.coolingOff = true
|
|
72
|
+
loopTracker.timers.push(
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
loopTracker.coolingOff = false
|
|
75
|
+
loopTracker.count = 0
|
|
76
|
+
}, loopTimeout)
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
88
81
|
async function attemptLoad() {
|
|
89
|
-
// If we're complete, don't attempt to load again
|
|
90
|
-
// If we're not ready (i.e. in the middle of a fetch) don't attempt to load again
|
|
91
|
-
// However, if we're in an error state, allow the user to retry via btn click
|
|
92
82
|
if (
|
|
93
83
|
loaderState.status === STATUS.COMPLETE ||
|
|
94
84
|
(loaderState.status !== STATUS.READY && loaderState.status !== STATUS.ERROR)
|
|
@@ -98,47 +88,36 @@
|
|
|
98
88
|
|
|
99
89
|
loaderState.status = STATUS.LOADING
|
|
100
90
|
|
|
101
|
-
// Skip loading if we're in infinite loop cool-off
|
|
102
91
|
if (!loopTracker.coolingOff) {
|
|
103
92
|
await triggerLoad()
|
|
104
|
-
|
|
93
|
+
trackLoad()
|
|
105
94
|
}
|
|
106
95
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (loaderState.status === STATUS.LOADING) {
|
|
111
|
-
loaderState.isFirstLoad = false
|
|
112
|
-
loaderState.status = STATUS.READY
|
|
113
|
-
}
|
|
96
|
+
if (loaderState.status === STATUS.LOADING) {
|
|
97
|
+
loaderState.isFirstLoad = false
|
|
98
|
+
loaderState.status = STATUS.READY
|
|
114
99
|
}
|
|
115
100
|
}
|
|
116
101
|
|
|
117
|
-
|
|
118
|
-
if (
|
|
102
|
+
$effect(() => {
|
|
103
|
+
if (!intersectionTarget) return
|
|
119
104
|
|
|
120
105
|
const appliedIntersectionOptions = {
|
|
121
106
|
rootMargin: "0px 0px 200px 0px",
|
|
122
107
|
...intersectionOptions
|
|
123
108
|
}
|
|
124
|
-
|
|
125
|
-
if (entries[0]?.isIntersecting
|
|
109
|
+
const obs = new IntersectionObserver(async (entries) => {
|
|
110
|
+
if (entries[0]?.isIntersecting) {
|
|
126
111
|
await attemptLoad()
|
|
127
112
|
}
|
|
128
113
|
}, appliedIntersectionOptions)
|
|
129
|
-
|
|
114
|
+
obs.observe(intersectionTarget)
|
|
130
115
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (observer) {
|
|
137
|
-
observer.disconnect()
|
|
138
|
-
}
|
|
139
|
-
if (loopTracker) {
|
|
140
|
-
loopTracker.destroy()
|
|
141
|
-
}
|
|
116
|
+
return () => {
|
|
117
|
+
obs.disconnect()
|
|
118
|
+
loopTracker.timers.forEach((timer) => {
|
|
119
|
+
if (timer !== null) clearTimeout(timer)
|
|
120
|
+
})
|
|
142
121
|
}
|
|
143
122
|
})
|
|
144
123
|
</script>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type Snippet } from "svelte";
|
|
2
2
|
import { LoaderState } from "./loaderState.svelte";
|
|
3
|
-
|
|
3
|
+
type InfiniteLoaderProps = {
|
|
4
4
|
triggerLoad: () => Promise<void>;
|
|
5
5
|
loopTimeout?: number;
|
|
6
6
|
loopDetectionTimeout?: number;
|
|
7
7
|
loopMaxCalls?: number;
|
|
8
|
-
intersectionOptions?:
|
|
8
|
+
intersectionOptions?: IntersectionObserverInit;
|
|
9
9
|
loaderState: LoaderState;
|
|
10
10
|
children: Snippet;
|
|
11
11
|
loading?: Snippet;
|
|
@@ -13,6 +13,7 @@ declare const InfiniteLoader: import("svelte").Component<{
|
|
|
13
13
|
noData?: Snippet;
|
|
14
14
|
coolingOff?: Snippet;
|
|
15
15
|
error?: Snippet<[() => Promise<void>]>;
|
|
16
|
-
}
|
|
16
|
+
};
|
|
17
|
+
declare const InfiniteLoader: import("svelte").Component<InfiniteLoaderProps, {}, "">;
|
|
17
18
|
type InfiniteLoader = ReturnType<typeof InfiniteLoader>;
|
|
18
19
|
export default InfiniteLoader;
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"email": "yo@ndo.dev",
|
|
7
7
|
"url": "https://ndo.dev"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.5.
|
|
9
|
+
"version": "0.5.2",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"homepage": "https://svelte-5-infinite.vercel.app",
|
|
12
12
|
"keywords": [
|
|
@@ -52,30 +52,30 @@
|
|
|
52
52
|
"svelte": "^5.0.0-0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@eslint/eslintrc": "^3.
|
|
56
|
-
"@eslint/js": "^9.
|
|
57
|
-
"@sveltejs/adapter-auto": "^
|
|
58
|
-
"@sveltejs/kit": "^2.
|
|
59
|
-
"@sveltejs/package": "^2.
|
|
60
|
-
"@sveltejs/vite-plugin-svelte": "^
|
|
55
|
+
"@eslint/eslintrc": "^3.3.3",
|
|
56
|
+
"@eslint/js": "^9.39.2",
|
|
57
|
+
"@sveltejs/adapter-auto": "^7.0.1",
|
|
58
|
+
"@sveltejs/kit": "^2.51.0",
|
|
59
|
+
"@sveltejs/package": "^2.5.7",
|
|
60
|
+
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
61
61
|
"@types/eslint": "^9.6.1",
|
|
62
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
63
|
-
"@typescript-eslint/parser": "^8.
|
|
64
|
-
"eslint": "^9.
|
|
65
|
-
"eslint-config-prettier": "^
|
|
66
|
-
"eslint-plugin-svelte": "^
|
|
67
|
-
"globals": "^
|
|
68
|
-
"prettier": "^3.
|
|
69
|
-
"prettier-plugin-svelte": "^3.
|
|
70
|
-
"publint": "^0.
|
|
71
|
-
"svelte": "^5.
|
|
72
|
-
"svelte-check": "^4.0
|
|
62
|
+
"@typescript-eslint/eslint-plugin": "^8.55.0",
|
|
63
|
+
"@typescript-eslint/parser": "^8.55.0",
|
|
64
|
+
"eslint": "^9.39.2",
|
|
65
|
+
"eslint-config-prettier": "^10.1.8",
|
|
66
|
+
"eslint-plugin-svelte": "^3.15.0",
|
|
67
|
+
"globals": "^17.3.0",
|
|
68
|
+
"prettier": "^3.8.1",
|
|
69
|
+
"prettier-plugin-svelte": "^3.4.1",
|
|
70
|
+
"publint": "^0.3.17",
|
|
71
|
+
"svelte": "^5.51.0",
|
|
72
|
+
"svelte-check": "^4.4.0",
|
|
73
73
|
"tslib": "^2.8.1",
|
|
74
|
-
"typescript": "^5.
|
|
75
|
-
"typescript-eslint": "^8.
|
|
76
|
-
"typescript-svelte-plugin": "^0.3.
|
|
77
|
-
"vite": "^
|
|
78
|
-
"vitest": "^
|
|
74
|
+
"typescript": "^5.9.3",
|
|
75
|
+
"typescript-eslint": "^8.55.0",
|
|
76
|
+
"typescript-svelte-plugin": "^0.3.50",
|
|
77
|
+
"vite": "^7.3.1",
|
|
78
|
+
"vitest": "^4.0.18"
|
|
79
79
|
},
|
|
80
80
|
"packageManager": "pnpm@9.0.6+sha256.0624e30eff866cdeb363b15061bdb7fd9425b17bc1bb42c22f5f4efdea21f6b3"
|
|
81
81
|
}
|