svelte-infinite 0.2.3 → 0.3.1
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 +32 -28
- package/dist/InfiniteLoader.svelte +44 -61
- package/dist/InfiniteLoader.svelte.d.ts +1 -9
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/loaderState.svelte.d.ts +16 -0
- package/dist/loaderState.svelte.js +26 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -12,15 +12,18 @@
|
|
|
12
12
|
[](https://npmjs.org/packages/svelte-infinite)
|
|
13
13
|
[](https://svelte-5-infinite.vercel.app)
|
|
14
14
|
|
|
15
|
-
> Svelte Infinite Loader designed and rebuilt specifically for use
|
|
15
|
+
> Svelte Infinite Loader designed and rebuilt specifically for use with **Svelte 5**
|
|
16
16
|
|
|
17
17
|
✨ Flexible
|
|
18
18
|
⏰ Infinite Loop Detection
|
|
19
19
|
📣 Control Loader State
|
|
20
20
|
🔎 `IntersectionObserver` based
|
|
21
|
+
🔥 Using Runes and Snippets
|
|
21
22
|
🧑🔧 **Demo**: [svelte-5-infinite.vercel.app](https://svelte-5-infinite.vercel.app)
|
|
22
23
|
|
|
23
|
-
Svelte 5 is still early days, but I couldn't find an infinite loader-type component that was maintained for the last few years of Svelte 4. So I had recently built this for a Svelte 5-based application I
|
|
24
|
+
Svelte 5 is still early days, but I couldn't find an infinite loader-type component that was maintained for the last few years of Svelte 4 even. So I had recently built this for a Svelte 5-based application I'm working on and was pretty happy with it, so I decided to share it with the world!
|
|
25
|
+
|
|
26
|
+
As Svelte 5 inevitably changes over the next weeks and months, I plan to keep this package updated and working with the latest available version of Svelte 5. Don't hesitate to open an issue if something has changed in the latest Svelte releases or you come across a bug!
|
|
24
27
|
|
|
25
28
|
## 🏗️ Getting Started
|
|
26
29
|
|
|
@@ -55,25 +58,26 @@ yarn add svelte-infinite
|
|
|
55
58
|
</InfiniteLoader>
|
|
56
59
|
```
|
|
57
60
|
|
|
58
|
-
The component should wrap your list of items, and `loaderState` should be used in your `triggerLoad` function (and/or elsewhere) to interact with the internal state of the Loader component. You tell it whether you're out of data, ran into an error, etc.
|
|
59
|
-
|
|
60
|
-
See the example below and [in this repository](https://github.com/ndom91/svelte-infinite/blob/main/src/routes/%2Bpage.svelte#L12-L50) for more details.
|
|
61
|
-
|
|
62
61
|
## 🍍 Example
|
|
63
62
|
|
|
63
|
+
This is a more realistic example use-case which includes a paginated data endpoint that your `triggerLoad` function should hit every time it's called to load more data. It also includes the use of some of the optional snippets to render custom markup inside the loader component.
|
|
64
|
+
|
|
64
65
|
```svelte
|
|
65
66
|
<script lang="ts">
|
|
67
|
+
// +page.svelte
|
|
68
|
+
|
|
66
69
|
import { InfiniteLoader, loaderState } from "svelte-infinite"
|
|
67
70
|
import UserCard from "$components/UserCard.svelte"
|
|
68
71
|
|
|
69
72
|
const LOAD_LIMIT = 20
|
|
73
|
+
// Assume `$page.data.items` is the `+page.server.ts` server-side loaded
|
|
74
|
+
// and rendered initial 20 items of the list
|
|
70
75
|
const allItems = $state<{ id: number, body: string }[]>($page.data.items)
|
|
71
76
|
let pageNumber = $state(1)
|
|
72
77
|
|
|
73
|
-
// 1.
|
|
78
|
+
// 1. This `loadMore` function is what we'll pass the InfiniteLoader component
|
|
74
79
|
// to its `triggerLoad` prop.
|
|
75
80
|
const loadMore = async () => {
|
|
76
|
-
// This is a relatively straight-forward load function with support for pagination
|
|
77
81
|
try {
|
|
78
82
|
pageNumber += 1
|
|
79
83
|
const limit = String(LOAD_LIMIT)
|
|
@@ -82,20 +86,21 @@ See the example below and [in this repository](https://github.com/ndom91/svelte-
|
|
|
82
86
|
// If there are less results on the first page (page.server loaded data)
|
|
83
87
|
// than the limit, don't keep trying to fetch more. We're done.
|
|
84
88
|
if (allItems.length < LOAD_LIMIT) {
|
|
85
|
-
loaderState.complete()
|
|
89
|
+
loaderState.complete() // <--- using loaderState
|
|
86
90
|
return
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
const searchParams = new URLSearchParams({ limit, skip })
|
|
90
94
|
|
|
91
|
-
//
|
|
95
|
+
// Fetch an endpoint that supports server-side pagination
|
|
92
96
|
const dataResponse = await fetch(`/api/data?${searchParams}`)
|
|
93
97
|
|
|
94
98
|
// Ideally, like most paginated endpoints, this should return the data
|
|
95
99
|
// you've requested for your page, as well as the total amount of data
|
|
96
100
|
// available to page through
|
|
101
|
+
|
|
97
102
|
if (!dataResponse.ok) {
|
|
98
|
-
loaderState.error()
|
|
103
|
+
loaderState.error() // <--- using loaderState
|
|
99
104
|
|
|
100
105
|
// On errors, set the pageNumber back so we can retry
|
|
101
106
|
// that page's data on the next 'loadMore' attempt
|
|
@@ -104,8 +109,7 @@ See the example below and [in this repository](https://github.com/ndom91/svelte-
|
|
|
104
109
|
}
|
|
105
110
|
const data = await dataResponse.json()
|
|
106
111
|
|
|
107
|
-
// If we've received data, push it to the reactive state variable
|
|
108
|
-
// rendering our items inside the `<InfiniteLoader />` below.
|
|
112
|
+
// If we've successfully received data, push it to the reactive state variable
|
|
109
113
|
if (data.items.length) {
|
|
110
114
|
allItems.push(...data.items)
|
|
111
115
|
}
|
|
@@ -113,13 +117,13 @@ See the example below and [in this repository](https://github.com/ndom91/svelte-
|
|
|
113
117
|
// If there are more (or equal) number of items loaded as are totally available
|
|
114
118
|
// from the API, don't keep trying to fetch more. We're done.
|
|
115
119
|
if (allItems.length >= data.totalCount) {
|
|
116
|
-
loaderState.complete()
|
|
120
|
+
loaderState.complete() // <--- using loaderState
|
|
117
121
|
} else {
|
|
118
|
-
loaderState.loaded()
|
|
122
|
+
loaderState.loaded() // <--- using loaderState
|
|
119
123
|
}
|
|
120
124
|
} catch (error) {
|
|
121
125
|
console.error(error)
|
|
122
|
-
loaderState.error()
|
|
126
|
+
loaderState.error() // <--- using loaderState
|
|
123
127
|
pageNumber -= 1
|
|
124
128
|
}
|
|
125
129
|
}
|
|
@@ -155,36 +159,36 @@ This package consists of two parts, first the `InfiniteLoader` component which i
|
|
|
155
159
|
|
|
156
160
|
Second, there is also a `loaderState` import which you should use to interact with the internal state of the loader. For example, if your `fetch` call errored, or you've reached the maximum number of items, etc. you can communicate that to the loader. The most basic usage example can be seen in the 'Getting Started' section above. A more complex example can be seen in the 'Example' section, and of course the application in `/src/routes/+page.svelte` in this repository also has a "real-world" usage example.
|
|
157
161
|
|
|
158
|
-
### loaderState
|
|
162
|
+
### `loaderState` Controller
|
|
159
163
|
|
|
160
|
-
The `loaderState`
|
|
164
|
+
The `loaderState` controller has 4 methods on it. You should call these at the appropriate times to control the internal state of the `InfiniteLoader`.
|
|
161
165
|
|
|
162
166
|
- `loaderState.loaded()`
|
|
163
|
-
- Designed to be called after a successful fetch.
|
|
167
|
+
- Designed to be called after a successful fetch. Will set the internal state back to `READY` so another fetch can be attempted.
|
|
164
168
|
- `loaderState.error()`
|
|
165
169
|
- Designed to be called after a failed fetch or any other error. This will cause the `InfiniteLoader` to render a "Retry" button by default, or the `error` snippet.
|
|
166
170
|
- `loaderState.complete()`
|
|
167
|
-
- Designed to be called when you've reached the end of your list and there are no more items to fetch. This will render a "No more data" string, or the `
|
|
171
|
+
- Designed to be called when you've reached the end of your list and there are no more items to fetch. This will render a "No more data" string, or the `noData` snippet.
|
|
168
172
|
- `loaderState.reset()`
|
|
169
|
-
- Designed to be called when you want to reset the state of the `InfiniteLoader` to its initial state, for example if there is a search input tied to your
|
|
173
|
+
- Designed to be called when you want to reset the state of the `InfiniteLoader` to its initial state, for example if there is a search input tied to your data and the user enters a new query.
|
|
170
174
|
|
|
171
|
-
### Props
|
|
175
|
+
### `InfiniteLoader` Props
|
|
172
176
|
|
|
173
177
|
- `triggerLoad: () => Promise<void>` - **required**
|
|
174
178
|
- The async function to call when we should attempt to load more data to show.
|
|
175
179
|
- `intersectionOptions: `[`IntersectionObserverInit`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver#options)` = { rootMargin: "0px 0px 200px 0px" }` - optional
|
|
176
180
|
- The options to pass to the `IntersectionObserver` instance. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver#options) for more details. The default `rootMargin` value will cause the target to intersect 200px earlier and trigger the `loadMore` function before it actually intersects with the root element (window by default). This has the effect of beginning to load the next page of data before the user has actually reached the current bottom of the list, making the experience feel more smooth.
|
|
177
|
-
-
|
|
181
|
+
- If you are using a separate scroll container (element with `overflow-y: scroll`) other than the window / viewport, then it might be necessary for you to also pass a [custom `root` element](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/root) here.
|
|
178
182
|
- `loopTimeout: number = 3000` - optional
|
|
179
|
-
-
|
|
183
|
+
- Length of the cool down period (in milliseconds).
|
|
180
184
|
- `loopDetectionTimeout: number = 2000` - optional
|
|
181
|
-
- The time in milliseconds in which the `loopMaxCalls` count must be hit in order to trigger a cool down period
|
|
185
|
+
- The time in milliseconds in which the `loopMaxCalls` count must be hit in order to trigger a cool down period.
|
|
182
186
|
- `loopMaxCalls: number = 5` - optional
|
|
183
|
-
- The
|
|
187
|
+
- The limit of `triggerLoad` executions which will trigger a cool down period, if reached within the `loopDetectionTimeout`.
|
|
184
188
|
|
|
185
|
-
### Snippets
|
|
189
|
+
### `InfiniteLoader` Snippets
|
|
186
190
|
|
|
187
|
-
Snippets [replace slots](https://svelte-5-preview.vercel.app/docs/snippets#snippets-and-slots) in Svelte 5, and as such are used here to customize the content shown at the bottom of the scroller in various states. The `InfiniteLoader` component has
|
|
191
|
+
Snippets [replace slots](https://svelte-5-preview.vercel.app/docs/snippets#snippets-and-slots) in Svelte 5, and as such are used here to customize the content shown at the bottom of the scroller in various states. The `InfiniteLoader` component has 5 snippet "slots" available.
|
|
188
192
|
|
|
189
193
|
- `loading`
|
|
190
194
|
- Shown while calling `triggerLoad` and waiting on a response.
|
|
@@ -1,31 +1,5 @@
|
|
|
1
|
-
<script context="module">const STATUS = {
|
|
2
|
-
READY: "READY",
|
|
3
|
-
LOADING: "LOADING",
|
|
4
|
-
COMPLETE: "COMPLETE",
|
|
5
|
-
ERROR: "ERROR"
|
|
6
|
-
};
|
|
7
|
-
let isFirstLoad = $state(true);
|
|
8
|
-
let status = $state(STATUS.READY);
|
|
9
|
-
export const loaderState = {
|
|
10
|
-
loaded: () => {
|
|
11
|
-
isFirstLoad = false;
|
|
12
|
-
status = STATUS.READY;
|
|
13
|
-
},
|
|
14
|
-
complete: () => {
|
|
15
|
-
isFirstLoad = false;
|
|
16
|
-
status = STATUS.COMPLETE;
|
|
17
|
-
},
|
|
18
|
-
reset: () => {
|
|
19
|
-
status = STATUS.READY;
|
|
20
|
-
isFirstLoad = true;
|
|
21
|
-
},
|
|
22
|
-
error: () => {
|
|
23
|
-
status = STATUS.ERROR;
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
</script>
|
|
27
|
-
|
|
28
1
|
<script>import { onMount, onDestroy } from "svelte";
|
|
2
|
+
import { STATUS, loaderState } from "./loaderState.svelte";
|
|
29
3
|
const {
|
|
30
4
|
triggerLoad,
|
|
31
5
|
loopTimeout = 3e3,
|
|
@@ -33,11 +7,11 @@ const {
|
|
|
33
7
|
loopMaxCalls = 5,
|
|
34
8
|
intersectionOptions = {},
|
|
35
9
|
children,
|
|
36
|
-
loading,
|
|
37
|
-
noResults,
|
|
38
|
-
noData,
|
|
39
|
-
coolingOff,
|
|
40
|
-
error
|
|
10
|
+
loading: loadingSnippet,
|
|
11
|
+
noResults: noResultsSnippet,
|
|
12
|
+
noData: noDataSnippet,
|
|
13
|
+
coolingOff: coolingOffSnippet,
|
|
14
|
+
error: errorSnippet
|
|
41
15
|
} = $props();
|
|
42
16
|
const ERROR_INFINITE_LOOP = `Attempted to execute load function ${loopMaxCalls} or more times within a short period. Please wait before trying again..`;
|
|
43
17
|
class LoopTracker {
|
|
@@ -45,6 +19,7 @@ class LoopTracker {
|
|
|
45
19
|
#coolingOffTimer = null;
|
|
46
20
|
#timer = null;
|
|
47
21
|
#count = 0;
|
|
22
|
+
// On each call, increment the count and reset the timer
|
|
48
23
|
track() {
|
|
49
24
|
this.#count += 1;
|
|
50
25
|
clearTimeout(this.#timer);
|
|
@@ -60,28 +35,32 @@ class LoopTracker {
|
|
|
60
35
|
}, loopTimeout);
|
|
61
36
|
}
|
|
62
37
|
}
|
|
38
|
+
destroy() {
|
|
39
|
+
this.#timer && clearTimeout(this.#timer);
|
|
40
|
+
this.#coolingOffTimer && clearTimeout(this.#coolingOffTimer);
|
|
41
|
+
}
|
|
63
42
|
}
|
|
64
43
|
const loopTracker = new LoopTracker();
|
|
65
44
|
let intersectionTarget = $state();
|
|
66
45
|
let observer = $state();
|
|
67
|
-
let showLoading = $derived(status === STATUS.LOADING);
|
|
68
|
-
let showError = $derived(status === STATUS.ERROR);
|
|
69
|
-
let showNoResults = $derived(status === STATUS.COMPLETE && isFirstLoad);
|
|
70
|
-
let showNoData = $derived(status === STATUS.COMPLETE && !isFirstLoad);
|
|
71
|
-
let showCoolingOff = $derived(status !== STATUS.COMPLETE && loopTracker.coolingOff);
|
|
46
|
+
let showLoading = $derived(loaderState.status === STATUS.LOADING);
|
|
47
|
+
let showError = $derived(loaderState.status === STATUS.ERROR);
|
|
48
|
+
let showNoResults = $derived(loaderState.status === STATUS.COMPLETE && loaderState.isFirstLoad);
|
|
49
|
+
let showNoData = $derived(loaderState.status === STATUS.COMPLETE && !loaderState.isFirstLoad);
|
|
50
|
+
let showCoolingOff = $derived(loaderState.status !== STATUS.COMPLETE && loopTracker.coolingOff);
|
|
72
51
|
async function attemptLoad() {
|
|
73
|
-
if (status === STATUS.COMPLETE || status !== STATUS.READY && status !== STATUS.ERROR) {
|
|
52
|
+
if (loaderState.status === STATUS.COMPLETE || loaderState.status !== STATUS.READY && loaderState.status !== STATUS.ERROR) {
|
|
74
53
|
return;
|
|
75
54
|
}
|
|
76
|
-
status = STATUS.LOADING;
|
|
55
|
+
loaderState.status = STATUS.LOADING;
|
|
77
56
|
if (!loopTracker.coolingOff) {
|
|
78
57
|
await triggerLoad();
|
|
79
58
|
loopTracker.track();
|
|
80
59
|
}
|
|
81
|
-
if (status !== STATUS.ERROR && status !== STATUS.COMPLETE) {
|
|
82
|
-
if (status === STATUS.LOADING) {
|
|
83
|
-
status = STATUS.READY;
|
|
84
|
-
isFirstLoad = false;
|
|
60
|
+
if (loaderState.status !== STATUS.ERROR && loaderState.status !== STATUS.COMPLETE) {
|
|
61
|
+
if (loaderState.status === STATUS.LOADING) {
|
|
62
|
+
loaderState.status = STATUS.READY;
|
|
63
|
+
loaderState.isFirstLoad = false;
|
|
85
64
|
}
|
|
86
65
|
}
|
|
87
66
|
}
|
|
@@ -100,55 +79,59 @@ onMount(() => {
|
|
|
100
79
|
observer.observe(intersectionTarget);
|
|
101
80
|
});
|
|
102
81
|
onDestroy(() => {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
82
|
+
observer && observer.disconnect();
|
|
83
|
+
loopTracker && loopTracker.destroy();
|
|
106
84
|
});
|
|
107
85
|
</script>
|
|
108
86
|
|
|
109
87
|
<div class="infinite-loader-wrapper">
|
|
88
|
+
<!-- Render the users list items -->
|
|
110
89
|
{@render children()}
|
|
111
90
|
|
|
112
91
|
<div class="infinite-intersection-target" bind:this={intersectionTarget}>
|
|
113
92
|
{#if showLoading}
|
|
114
|
-
{#if
|
|
115
|
-
{@render
|
|
93
|
+
{#if loadingSnippet}
|
|
94
|
+
{@render loadingSnippet()}
|
|
116
95
|
{:else}
|
|
117
96
|
<div class="infinite-loading">Loading...</div>
|
|
118
97
|
{/if}
|
|
119
98
|
{/if}
|
|
120
99
|
|
|
121
100
|
{#if showNoResults}
|
|
122
|
-
{#if
|
|
123
|
-
{@render
|
|
101
|
+
{#if noResultsSnippet}
|
|
102
|
+
{@render noResultsSnippet()}
|
|
124
103
|
{:else}
|
|
125
104
|
<div class="infinite-no-results">No results</div>
|
|
126
105
|
{/if}
|
|
127
106
|
{/if}
|
|
128
107
|
|
|
129
108
|
{#if showNoData}
|
|
130
|
-
{#if
|
|
131
|
-
{@render
|
|
109
|
+
{#if noDataSnippet}
|
|
110
|
+
{@render noDataSnippet()}
|
|
132
111
|
{:else}
|
|
133
112
|
<div class="infinite-no-data">No more data</div>
|
|
134
113
|
{/if}
|
|
135
114
|
{/if}
|
|
136
115
|
|
|
137
116
|
{#if showCoolingOff}
|
|
138
|
-
{#if
|
|
139
|
-
{@render
|
|
117
|
+
{#if coolingOffSnippet}
|
|
118
|
+
{@render coolingOffSnippet()}
|
|
140
119
|
{:else}
|
|
141
120
|
<div class="infinite-cooling-off">Potential loop detected, please wait and try again..</div>
|
|
142
121
|
{/if}
|
|
143
122
|
{/if}
|
|
144
123
|
|
|
145
124
|
{#if showError}
|
|
146
|
-
{#if
|
|
147
|
-
{@render
|
|
125
|
+
{#if errorSnippet}
|
|
126
|
+
{@render errorSnippet(attemptLoad)}
|
|
148
127
|
{:else}
|
|
149
128
|
<div class="infinite-error">
|
|
150
|
-
<div class="infinite-
|
|
151
|
-
<button
|
|
129
|
+
<div class="infinite-error__label">Oops, something went wrong</div>
|
|
130
|
+
<button
|
|
131
|
+
class="infinite-error__btn"
|
|
132
|
+
disabled={loaderState.status === STATUS.COMPLETE}
|
|
133
|
+
onclick={attemptLoad}
|
|
134
|
+
>
|
|
152
135
|
Retry
|
|
153
136
|
</button>
|
|
154
137
|
</div>
|
|
@@ -184,11 +167,11 @@ onDestroy(() => {
|
|
|
184
167
|
font-size: 1.5rem;
|
|
185
168
|
margin-block: 1rem;
|
|
186
169
|
|
|
187
|
-
.infinite-
|
|
170
|
+
.infinite-error__label {
|
|
188
171
|
color: crimson;
|
|
189
172
|
}
|
|
190
173
|
|
|
191
|
-
.infinite-
|
|
174
|
+
.infinite-error__btn {
|
|
192
175
|
color: white;
|
|
193
176
|
background-color: #333;
|
|
194
177
|
padding-inline: 1.5rem;
|
|
@@ -198,7 +181,7 @@ onDestroy(() => {
|
|
|
198
181
|
transition: background-color 0.3s;
|
|
199
182
|
line-height: normal;
|
|
200
183
|
}
|
|
201
|
-
.infinite-
|
|
184
|
+
.infinite-error__btn:hover {
|
|
202
185
|
cursor: pointer;
|
|
203
186
|
background-color: #222;
|
|
204
187
|
}
|
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
import { SvelteComponent } from "svelte";
|
|
2
|
-
export declare const loaderState: {
|
|
3
|
-
loaded: () => void;
|
|
4
|
-
complete: () => void;
|
|
5
|
-
reset: () => void;
|
|
6
|
-
error: () => void;
|
|
7
|
-
};
|
|
8
2
|
import { type Snippet } from "svelte";
|
|
9
3
|
declare const __propDef: {
|
|
10
4
|
props: {
|
|
@@ -26,9 +20,7 @@ declare const __propDef: {
|
|
|
26
20
|
coolingOff?: ((this: void) => typeof import("svelte").SnippetReturn & {
|
|
27
21
|
_: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
|
|
28
22
|
}) | undefined;
|
|
29
|
-
error?: ((this: void, args_0: {
|
|
30
|
-
attemptLoad: Promise<() => void>;
|
|
31
|
-
}) => typeof import("svelte").SnippetReturn & {
|
|
23
|
+
error?: ((this: void, args_0: () => Promise<void>) => typeof import("svelte").SnippetReturn & {
|
|
32
24
|
_: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
|
|
33
25
|
}) | undefined;
|
|
34
26
|
};
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const STATUS: {
|
|
2
|
+
readonly READY: "READY";
|
|
3
|
+
readonly LOADING: "LOADING";
|
|
4
|
+
readonly COMPLETE: "COMPLETE";
|
|
5
|
+
readonly ERROR: "ERROR";
|
|
6
|
+
};
|
|
7
|
+
declare class LoaderState {
|
|
8
|
+
isFirstLoad: boolean;
|
|
9
|
+
status: "READY" | "LOADING" | "COMPLETE" | "ERROR";
|
|
10
|
+
loaded: () => void;
|
|
11
|
+
complete: () => void;
|
|
12
|
+
reset: () => void;
|
|
13
|
+
error: () => void;
|
|
14
|
+
}
|
|
15
|
+
export declare const loaderState: LoaderState;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const STATUS = {
|
|
2
|
+
READY: "READY",
|
|
3
|
+
LOADING: "LOADING",
|
|
4
|
+
COMPLETE: "COMPLETE",
|
|
5
|
+
ERROR: "ERROR"
|
|
6
|
+
};
|
|
7
|
+
class LoaderState {
|
|
8
|
+
isFirstLoad = $state(true);
|
|
9
|
+
status = $state(STATUS.READY);
|
|
10
|
+
loaded = () => {
|
|
11
|
+
this.isFirstLoad = false;
|
|
12
|
+
this.status = STATUS.READY;
|
|
13
|
+
};
|
|
14
|
+
complete = () => {
|
|
15
|
+
this.isFirstLoad = false;
|
|
16
|
+
this.status = STATUS.COMPLETE;
|
|
17
|
+
};
|
|
18
|
+
reset = () => {
|
|
19
|
+
this.isFirstLoad = true;
|
|
20
|
+
this.status = STATUS.READY;
|
|
21
|
+
};
|
|
22
|
+
error = () => {
|
|
23
|
+
this.status = STATUS.ERROR;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export const loaderState = new LoaderState();
|
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.
|
|
9
|
+
"version": "0.3.1",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"homepage": "https://svelte-5-infinite.vercel.app",
|
|
12
12
|
"keywords": [
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"svelte-check": "^3.6.6",
|
|
58
58
|
"tslib": "^2.6.2",
|
|
59
59
|
"typescript": "^5.3.3",
|
|
60
|
+
"typescript-svelte-plugin": "^0.3.37",
|
|
60
61
|
"vite": "^5.1.4",
|
|
61
62
|
"vitest": "^1.3.1"
|
|
62
63
|
},
|