veload 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +699 -3
- package/dist/components/VLoader/index.vue.d.ts +12 -0
- package/dist/components/VLoader/loaders.d.ts +38 -0
- package/dist/components/VLoader/types.d.ts +28 -0
- package/dist/components/VLoading/index.vue.d.ts +21 -0
- package/dist/components/VLoading/types.d.ts +27 -0
- package/dist/components/VLoadingButton/index.vue.d.ts +21 -0
- package/dist/components/VLoadingButton/types.d.ts +29 -0
- package/dist/components/VPatternSkeleton/index.vue.d.ts +18 -0
- package/dist/components/VPatternSkeleton/presets.d.ts +26 -0
- package/dist/components/VPatternSkeleton/types.d.ts +48 -0
- package/dist/components/VProgress/index.vue.d.ts +10 -0
- package/dist/components/VProgress/types.d.ts +45 -0
- package/dist/components/VSkeleton/index.vue.d.ts +7 -0
- package/dist/components/VSkeleton/types.d.ts +37 -0
- package/dist/components/VSkeletonGroup/index.vue.d.ts +17 -0
- package/dist/components/VSkeletonGroup/types.d.ts +8 -0
- package/dist/components/VSkeletonSwitch/index.vue.d.ts +31 -0
- package/dist/components/VSkeletonSwitch/types.d.ts +5 -0
- package/dist/components/VSpinner/index.vue.d.ts +9 -0
- package/dist/components/VSpinner/types.d.ts +29 -0
- package/dist/composables/useDelayedLoading.d.ts +32 -0
- package/dist/composables/useLoading.d.ts +28 -0
- package/dist/composables/useLoadingGroup.d.ts +25 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +851 -0
- package/dist/style.css +2 -0
- package/dist/utils/css.d.ts +7 -0
- package/package.json +56 -6
package/README.md
CHANGED
|
@@ -1,3 +1,699 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# Veloader
|
|
2
|
+
|
|
3
|
+
A small, accessible loading toolkit for Vue 3, written in TypeScript.
|
|
4
|
+
|
|
5
|
+
Veloader provides:
|
|
6
|
+
|
|
7
|
+
- `VSpinner` — an animated status spinner
|
|
8
|
+
- `VSkeleton` — text, rectangular, and circular placeholders
|
|
9
|
+
- `VProgress` — determinate and indeterminate progress bars
|
|
10
|
+
- `VLoading` — a content wrapper with a loading overlay
|
|
11
|
+
- `VLoadingButton` — a native button with a loading state
|
|
12
|
+
- `useLoading()` — concurrent async operation tracking
|
|
13
|
+
- `useDelayedLoading()` — delayed and minimum-duration indicator visibility
|
|
14
|
+
- `useLoadingGroup()` — aggregation of independent loading states
|
|
15
|
+
|
|
16
|
+
> **Release status:** Veloader is licensed under MIT and is being prepared
|
|
17
|
+
> for its first public npm release. It is not yet published, so the install
|
|
18
|
+
> command below becomes usable only after the release is verified on npm.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
The workflow below is intended only after a public release is verified on
|
|
23
|
+
npm; consumers can then install the published package with:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install veload
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Veloader's actual Vue peer range is **`^3.5.2`**: Vue 3.5.2 or a later
|
|
30
|
+
compatible Vue 3 release, below Vue 4. Vue 3.0–3.4 are not supported by this
|
|
31
|
+
range. The generated declarations rely on Vue 3.5 type definitions. Use a
|
|
32
|
+
Vue application toolchain such as Vite with its Vue plugin or Nuxt; the
|
|
33
|
+
library itself ships compiled JavaScript, not SFCs consumers need to build.
|
|
34
|
+
|
|
35
|
+
No local library build, repository checkout, linking, or tarball is part of
|
|
36
|
+
the future public consumer workflow. Authorized repository development is
|
|
37
|
+
covered separately under [Local maintainer workflow](#local-maintainer-workflow).
|
|
38
|
+
|
|
39
|
+
The public entry points are:
|
|
40
|
+
|
|
41
|
+
| Import | Exported files |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| `veload` | Named ESM exports (`dist/index.js`), CommonJS (`dist/index.cjs`), and declarations (`dist/index.d.ts`). |
|
|
44
|
+
| `veload/style.css` | The extracted component stylesheet (`dist/style.css`). |
|
|
45
|
+
|
|
46
|
+
Import from these public paths, not internal source or `dist` subpaths.
|
|
47
|
+
|
|
48
|
+
### Stylesheet
|
|
49
|
+
|
|
50
|
+
Component styles are extracted into `dist/style.css`. Import it once in your
|
|
51
|
+
application entry point or root component:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import "veload/style.css";
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The JavaScript entry does not inject styles automatically. Without this import
|
|
58
|
+
the components render without their intended appearance and animations.
|
|
59
|
+
|
|
60
|
+
The stylesheet contains the styles of **every** component and is not split
|
|
61
|
+
per component. The ESM JavaScript entry point is tree-shakeable, so a
|
|
62
|
+
production bundler can remove unused components, but that does not trim the
|
|
63
|
+
shared CSS. Maintainers can run `npm run check:bundle` and
|
|
64
|
+
`npm run check:consumer` for measurements against the current packed build;
|
|
65
|
+
payload sizes change as the collection grows.
|
|
66
|
+
|
|
67
|
+
## Quick start
|
|
68
|
+
|
|
69
|
+
```vue
|
|
70
|
+
<script setup lang="ts">
|
|
71
|
+
import { ref } from "vue";
|
|
72
|
+
import { VLoading, useLoading } from "veload";
|
|
73
|
+
import "veload/style.css";
|
|
74
|
+
|
|
75
|
+
const { isLoading, execute } = useLoading();
|
|
76
|
+
const message = ref<string | null>(null);
|
|
77
|
+
|
|
78
|
+
async function load() {
|
|
79
|
+
try {
|
|
80
|
+
message.value = await execute(async () => {
|
|
81
|
+
const response = await fetch("/api/message");
|
|
82
|
+
if (!response.ok) throw new Error("Request failed");
|
|
83
|
+
return (await response.json()) as string;
|
|
84
|
+
});
|
|
85
|
+
} catch (error) {
|
|
86
|
+
message.value = error instanceof Error ? error.message : "Unknown error";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<template>
|
|
92
|
+
<VLoading :loading="isLoading" label="Loading message">
|
|
93
|
+
<button type="button" @click="load">Reload</button>
|
|
94
|
+
<p v-if="message">{{ message }}</p>
|
|
95
|
+
</VLoading>
|
|
96
|
+
</template>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Supply your own `/api/message` endpoint returning a JSON string; it is not
|
|
100
|
+
provided by Veloader. The request runs only on a user click, and the
|
|
101
|
+
application handles its errors. In `<script setup>`, imported components are
|
|
102
|
+
available in the template without registration.
|
|
103
|
+
|
|
104
|
+
### Optional component registration
|
|
105
|
+
|
|
106
|
+
All components are named exports. There is no default plugin export, global
|
|
107
|
+
plugin installation step, or global loading manager. For Options API
|
|
108
|
+
components, import the components and add them to `components`. To opt into
|
|
109
|
+
Vue's global registration instead, register individual exports in `main.ts`:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import { createApp } from "vue";
|
|
113
|
+
import { VSpinner } from "veload";
|
|
114
|
+
import "veload/style.css";
|
|
115
|
+
import App from "./App.vue";
|
|
116
|
+
|
|
117
|
+
const app = createApp(App);
|
|
118
|
+
app.component("VSpinner", VSpinner);
|
|
119
|
+
app.mount("#app");
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Do not call `app.use(Veloader)`; there is no Veloader plugin. Runtime global
|
|
123
|
+
registration does not automatically declare components for Vue's template
|
|
124
|
+
type checker. Prefer local imports, or add a Vue `GlobalComponents` type
|
|
125
|
+
augmentation in the consuming app if global template typing is needed.
|
|
126
|
+
|
|
127
|
+
### Nuxt and SSR
|
|
128
|
+
|
|
129
|
+
Register the stylesheet once through Nuxt's global CSS option:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
// nuxt.config.ts
|
|
133
|
+
export default defineNuxtConfig({
|
|
134
|
+
css: ["veload/style.css"],
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Import components directly in page or component SFCs. No Nuxt module,
|
|
139
|
+
client-only plugin, or `<ClientOnly>` wrapper is required to render them.
|
|
140
|
+
Use Nuxt's `useFetch` or `useAsyncData` for initial server data so Nuxt reuses
|
|
141
|
+
the server payload during hydration:
|
|
142
|
+
|
|
143
|
+
```vue
|
|
144
|
+
<script setup lang="ts">
|
|
145
|
+
import { VLoading } from "veload";
|
|
146
|
+
|
|
147
|
+
const { data: message, status, error, refresh } =
|
|
148
|
+
await useFetch<string>("/api/message");
|
|
149
|
+
</script>
|
|
150
|
+
|
|
151
|
+
<template>
|
|
152
|
+
<VLoading :loading="status === 'pending'" label="Loading message">
|
|
153
|
+
<button type="button" @click="refresh()">Reload</button>
|
|
154
|
+
<p v-if="error">Could not load the message.</p>
|
|
155
|
+
<p v-else-if="message">{{ message }}</p>
|
|
156
|
+
</VLoading>
|
|
157
|
+
</template>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The endpoint is application-owned. Create loading composables inside setup,
|
|
161
|
+
not at module scope where their mutable state could leak across SSR
|
|
162
|
+
requests. `useLoading` and `useDelayedLoading` track user-triggered work;
|
|
163
|
+
they do not replace Nuxt's data fetching or payload handling. Keep
|
|
164
|
+
browser-only code in event handlers or `onMounted` and use matching initial
|
|
165
|
+
state on the server and client to avoid hydration mismatches.
|
|
166
|
+
|
|
167
|
+
## Components
|
|
168
|
+
|
|
169
|
+
### `VSpinner`
|
|
170
|
+
|
|
171
|
+
An accessible CSS spinner with a customizable size and accent color.
|
|
172
|
+
|
|
173
|
+
```vue
|
|
174
|
+
<VSpinner />
|
|
175
|
+
<VSpinner :size="40" color="#22C55E" label="Loading products" />
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
| Prop | Type | Default | Description |
|
|
179
|
+
|---|---|---|---|
|
|
180
|
+
| `size` | `number` | `24` | Diameter in pixels. Negative and non-finite values are normalized. |
|
|
181
|
+
| `color` | `string` | `"currentColor"` | Accent color of the active edge. |
|
|
182
|
+
| `label` | `string` | `"Loading"` | Accessible name for `role="status"`. A blank label makes the spinner decorative. |
|
|
183
|
+
| `decorative` | `boolean` | `false` | Hides the spinner from assistive technology. Use when an ancestor already announces loading. |
|
|
184
|
+
|
|
185
|
+
The visual ring is always `aria-hidden`; only the root status element is
|
|
186
|
+
announced. The spinner itself does not animate under
|
|
187
|
+
`prefers-reduced-motion: reduce`.
|
|
188
|
+
|
|
189
|
+
### `VSkeleton`
|
|
190
|
+
|
|
191
|
+
A placeholder primitive for text, rectangles, and circles.
|
|
192
|
+
|
|
193
|
+
```vue
|
|
194
|
+
<!-- A single line of text -->
|
|
195
|
+
<VSkeleton />
|
|
196
|
+
|
|
197
|
+
<!-- A card-sized rectangle -->
|
|
198
|
+
<VSkeleton variant="rect" width="100%" height="12rem" />
|
|
199
|
+
|
|
200
|
+
<!-- An avatar -->
|
|
201
|
+
<VSkeleton variant="circle" :width="48" label="Loading avatar" />
|
|
202
|
+
|
|
203
|
+
<!-- Static, no shimmer -->
|
|
204
|
+
<VSkeleton variant="rect" :animated="false" height="2rem" />
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
| Prop | Type | Default | Description |
|
|
208
|
+
|---|---|---|---|
|
|
209
|
+
| `variant` | `"text" \| "rect" \| "circle"` | `"text"` | Placeholder shape. Unknown runtime values fall back to `"text"`. |
|
|
210
|
+
| `width` | `number \| string` | `100%` (`40px` for circles) | Pixel number or CSS length. |
|
|
211
|
+
| `height` | `number \| string` | `1em` for text, `6rem` for rectangles, square for circles | Pixel number or CSS length. |
|
|
212
|
+
| `borderRadius` | `number \| string` | Shape-dependent | Overrides the default radius. |
|
|
213
|
+
| `animated` | `boolean` | `true` | Enables the shimmer. Reduced-motion preferences always disable it. |
|
|
214
|
+
| `label` | `string` | — | When provided, the skeleton becomes a `role="status"` placeholder. Without a label it is `aria-hidden`. |
|
|
215
|
+
|
|
216
|
+
For simple layouts, compose the primitive with application spacing. For common
|
|
217
|
+
application layouts, see the named skeleton presets in the [Skeleton collection](#skeleton-collection).
|
|
218
|
+
|
|
219
|
+
### `VProgress`
|
|
220
|
+
|
|
221
|
+
A progress bar for known and unknown totals.
|
|
222
|
+
|
|
223
|
+
```vue
|
|
224
|
+
<!-- Determinate -->
|
|
225
|
+
<VProgress :value="42" label="Uploading files" value-text="42%" />
|
|
226
|
+
|
|
227
|
+
<!-- Indeterminate -->
|
|
228
|
+
<VProgress label="Preparing download" />
|
|
229
|
+
|
|
230
|
+
<!-- Custom range and appearance -->
|
|
231
|
+
<VProgress
|
|
232
|
+
:value="3"
|
|
233
|
+
:max="10"
|
|
234
|
+
label="Steps completed"
|
|
235
|
+
value-text="3 of 10 steps"
|
|
236
|
+
height="1rem"
|
|
237
|
+
color="#16A34A"
|
|
238
|
+
/>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
| Prop | Type | Default | Description |
|
|
242
|
+
|---|---|---|---|
|
|
243
|
+
| `value` | `number \| null` | `null` | Current value. Omit or pass `null`/`NaN` for indeterminate progress. |
|
|
244
|
+
| `min` | `number` | `0` | Range lower bound. |
|
|
245
|
+
| `max` | `number` | `100` | Range upper bound. |
|
|
246
|
+
| `label` | `string` | `"Progress"` | Accessible name. A blank label omits `aria-label`. |
|
|
247
|
+
| `valueText` | `string` | — | Human-readable value exposed through `aria-valuetext`. Only used while determinate. |
|
|
248
|
+
| `height` | `number \| string` | `8` | Bar thickness in pixels or any CSS length. |
|
|
249
|
+
| `color` | `string` | `#2563eb` | Filled bar color. Overrides `--v-progress-color`. |
|
|
250
|
+
| `trackColor` | `string` | `#e2e8f0` | Track color. Overrides `--v-progress-track-color`. |
|
|
251
|
+
|
|
252
|
+
Values are clamped to `[min, max]`. A non-finite value or an invalid range
|
|
253
|
+
(`max <= min`) produces indeterminate progress with no `aria-valuenow`.
|
|
254
|
+
Indeterminate progress does not claim a completion percentage.
|
|
255
|
+
|
|
256
|
+
### `VLoading`
|
|
257
|
+
|
|
258
|
+
A positioned overlay around content.
|
|
259
|
+
|
|
260
|
+
```vue
|
|
261
|
+
<VLoading :loading="isLoading" label="Loading products">
|
|
262
|
+
<ProductList />
|
|
263
|
+
</VLoading>
|
|
264
|
+
|
|
265
|
+
<!-- Keep content interactive while the overlay is visible -->
|
|
266
|
+
<VLoading :loading="isLoading" :inert-content="false" label="Refreshing">
|
|
267
|
+
<ProductList />
|
|
268
|
+
</VLoading>
|
|
269
|
+
|
|
270
|
+
<!-- Custom indicator -->
|
|
271
|
+
<VLoading :loading="isLoading" label="Loading products">
|
|
272
|
+
<ProductList />
|
|
273
|
+
|
|
274
|
+
<template #indicator>
|
|
275
|
+
<span class="my-loader">Please wait…</span>
|
|
276
|
+
</template>
|
|
277
|
+
</VLoading>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
| Prop | Type | Default | Description |
|
|
281
|
+
|---|---|---|---|
|
|
282
|
+
| `loading` | `boolean` | `false` | Shows the overlay and sets `aria-busy`. |
|
|
283
|
+
| `label` | `string` | `"Loading"` | Loading message. Visually hidden, announced by `role="status"`. A blank label removes the status role and hides the overlay from assistive technology. |
|
|
284
|
+
| `inertContent` | `boolean` | `true` | Makes wrapped content inert while loading. |
|
|
285
|
+
| `indicatorSize` | `number` | `28` | Size of the built-in spinner. |
|
|
286
|
+
|
|
287
|
+
Slots:
|
|
288
|
+
|
|
289
|
+
- `default` — the content being loaded.
|
|
290
|
+
- `indicator` — replaces the built-in spinner.
|
|
291
|
+
|
|
292
|
+
The overlay is absolutely positioned, so showing and hiding it does not change
|
|
293
|
+
the content's layout. The wrapper establishes a stacking context
|
|
294
|
+
(`isolation: isolate`) and the overlay defaults to `z-index: 10`. Give the
|
|
295
|
+
wrapper a height (or content) when it is not otherwise sized. When
|
|
296
|
+
`inertContent` is `true` (the default), the content is inert and the overlay
|
|
297
|
+
intercepts pointer events; when `false`, the overlay does not intercept pointer
|
|
298
|
+
events and content remains interactive. `inert` is supported by modern
|
|
299
|
+
browsers; older browsers may still allow interaction with content beneath the
|
|
300
|
+
overlay.
|
|
301
|
+
|
|
302
|
+
### `VLoadingButton`
|
|
303
|
+
|
|
304
|
+
A native `<button>` with a loading state.
|
|
305
|
+
|
|
306
|
+
```vue
|
|
307
|
+
<script setup lang="ts">
|
|
308
|
+
import { VLoadingButton, useLoading } from "veload";
|
|
309
|
+
|
|
310
|
+
const { isLoading, execute } = useLoading();
|
|
311
|
+
|
|
312
|
+
function save() {
|
|
313
|
+
return execute(async () => {
|
|
314
|
+
await fetch("/api/save", { method: "POST" });
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
</script>
|
|
318
|
+
|
|
319
|
+
<template>
|
|
320
|
+
<VLoadingButton :loading="isLoading" type="submit" @click="save">
|
|
321
|
+
Save changes
|
|
322
|
+
</VLoadingButton>
|
|
323
|
+
</template>
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
| Prop | Type | Default | Description |
|
|
327
|
+
|---|---|---|---|
|
|
328
|
+
| `loading` | `boolean` | `false` | Disables the button, sets `aria-busy="true"`, and shows an indicator. |
|
|
329
|
+
| `disabled` | `boolean` | `false` | Native disabled state. The button is also disabled while loading. |
|
|
330
|
+
| `type` | `"button" \| "submit" \| "reset"` | `"button"` | Native button type. The safe default avoids accidental form submission. |
|
|
331
|
+
| `indicatorSize` | `number` | `16` | Size of the built-in spinner. |
|
|
332
|
+
|
|
333
|
+
Slots:
|
|
334
|
+
|
|
335
|
+
- `default` — button label/content. It remains in the accessibility tree and
|
|
336
|
+
keeps its space while loading.
|
|
337
|
+
- `indicator` — replaces the built-in spinner.
|
|
338
|
+
|
|
339
|
+
Native attributes and event listeners are forwarded to the `<button>` element.
|
|
340
|
+
While loading, the native `disabled` attribute prevents repeated activation.
|
|
341
|
+
The component deliberately has no action callback prop: pass your async
|
|
342
|
+
handler through native listeners so errors propagate to your own code.
|
|
343
|
+
|
|
344
|
+
## Composables
|
|
345
|
+
|
|
346
|
+
### `useLoading()`
|
|
347
|
+
|
|
348
|
+
Tracks concurrent asynchronous operations for one loading state.
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
import { useLoading } from "veload";
|
|
352
|
+
|
|
353
|
+
const { isLoading, pendingCount, execute } = useLoading();
|
|
354
|
+
|
|
355
|
+
const result = await execute(() => fetchData());
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
| Member | Type | Description |
|
|
359
|
+
|---|---|---|
|
|
360
|
+
| `isLoading` | `Readonly<Ref<boolean>>` | `true` while at least one operation is pending. |
|
|
361
|
+
| `pendingCount` | `Readonly<Ref<number>>` | Number of pending operations for this instance. |
|
|
362
|
+
| `execute` | `<T>(operation: () => Promise<T>) => Promise<T>` | Runs and tracks the operation. Results and errors pass through unchanged. |
|
|
363
|
+
|
|
364
|
+
Each instance owns independent state. Overlapping calls are counted, and
|
|
365
|
+
`isLoading` stays `true` until the last operation settles. Synchronous throws
|
|
366
|
+
from the callback are also cleaned up and propagated.
|
|
367
|
+
|
|
368
|
+
`execute` continues to settle correctly when called outside an active Vue
|
|
369
|
+
effect scope. State is not automatically reset when a scope is disposed;
|
|
370
|
+
pending operations still decrement their own count when they finish.
|
|
371
|
+
|
|
372
|
+
### `useDelayedLoading(options?)`
|
|
373
|
+
|
|
374
|
+
Prevents loading-indicator flicker for fast operations.
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
import { useDelayedLoading } from "veload";
|
|
378
|
+
|
|
379
|
+
const { isLoading, pendingCount, execute } = useDelayedLoading({
|
|
380
|
+
delay: 200,
|
|
381
|
+
minVisibleDuration: 300,
|
|
382
|
+
});
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
`options` may also be a number, which is shorthand for `{ delay: number }`.
|
|
386
|
+
|
|
387
|
+
| Option | Type | Default | Description |
|
|
388
|
+
|---|---|---|---|
|
|
389
|
+
| `delay` | `number` | `200` | Milliseconds to wait before `isLoading` becomes `true`. Negative values become `0`; non-finite values use the default. |
|
|
390
|
+
| `minVisibleDuration` | `number` | `0` | Minimum milliseconds the indicator remains visible once shown. Negative values become `0`; non-finite values become `0`. |
|
|
391
|
+
|
|
392
|
+
Behavior:
|
|
393
|
+
|
|
394
|
+
- The operation starts immediately; only indicator visibility is delayed.
|
|
395
|
+
- If all operations finish before `delay`, the indicator never appears.
|
|
396
|
+
- Once visible, the indicator stays visible while any operation is pending.
|
|
397
|
+
- `minVisibleDuration` is measured from the moment the indicator appears, not
|
|
398
|
+
from when each operation starts. It is a minimum for continuous visibility:
|
|
399
|
+
if the indicator has already been visible that long, it hides as soon as all
|
|
400
|
+
operations finish.
|
|
401
|
+
- A new operation starting while a hide is pending cancels that hide and keeps
|
|
402
|
+
the indicator visible.
|
|
403
|
+
- Timers are cleared when operations finish or the owning component/effect
|
|
404
|
+
scope is disposed. Disposing a scope hides a visible indicator immediately.
|
|
405
|
+
- Results and errors are preserved exactly as with `useLoading()`.
|
|
406
|
+
|
|
407
|
+
### `useLoadingGroup(sources)`
|
|
408
|
+
|
|
409
|
+
Aggregates independent loading sources without owning them.
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
import { useLoading, useLoadingGroup } from "veload";
|
|
413
|
+
|
|
414
|
+
const products = useLoading();
|
|
415
|
+
const categories = useLoading();
|
|
416
|
+
|
|
417
|
+
const { isLoading, activeCount } = useLoadingGroup([
|
|
418
|
+
products.isLoading,
|
|
419
|
+
categories.isLoading,
|
|
420
|
+
]);
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
`sources` is either an array of `Readonly<Ref<boolean>>` / `() => boolean`
|
|
424
|
+
values, or a getter returning such an array when the set changes dynamically.
|
|
425
|
+
|
|
426
|
+
| Member | Type | Description |
|
|
427
|
+
|---|---|---|
|
|
428
|
+
| `isLoading` | `ComputedRef<boolean>` | `true` when at least one source is active. |
|
|
429
|
+
| `activeCount` | `ComputedRef<number>` | Number of active sources. |
|
|
430
|
+
|
|
431
|
+
The group is read-only: it does not mutate, start, or stop the source states.
|
|
432
|
+
|
|
433
|
+
## Accessibility
|
|
434
|
+
|
|
435
|
+
- `VSpinner` uses `role="status"` with an accessible label. Set `decorative`
|
|
436
|
+
or use a blank label when a parent already announces loading.
|
|
437
|
+
- `VSkeleton` is decorative by default. Provide `label` to expose it as a
|
|
438
|
+
`role="status"` placeholder with `aria-busy="true"`.
|
|
439
|
+
- `VProgress` uses `role="progressbar"` and exposes
|
|
440
|
+
`aria-valuemin`/`aria-valuemax`/`aria-valuenow` only when determinate.
|
|
441
|
+
`aria-valuetext` is available for human-readable values.
|
|
442
|
+
- `VLoading` sets `aria-busy` on its wrapper and announces its label through
|
|
443
|
+
`role="status"`. Its built-in spinner is decorative to avoid duplicate
|
|
444
|
+
announcements. With a blank label the overlay is `aria-hidden`.
|
|
445
|
+
- `VLoadingButton` exposes `aria-busy` while loading and relies on the native
|
|
446
|
+
`disabled` attribute to prevent repeated activation. Its label remains
|
|
447
|
+
available to assistive technology.
|
|
448
|
+
- All animations are disabled or reduced under
|
|
449
|
+
`prefers-reduced-motion: reduce`. Consumers can still add their own motion
|
|
450
|
+
preferences.
|
|
451
|
+
|
|
452
|
+
These behaviors are covered by unit tests, but no formal WCAG conformance
|
|
453
|
+
audit has been performed.
|
|
454
|
+
|
|
455
|
+
## Customization
|
|
456
|
+
|
|
457
|
+
Components use scoped styles and documented CSS custom properties. Set them on
|
|
458
|
+
a component or an ancestor:
|
|
459
|
+
|
|
460
|
+
```vue
|
|
461
|
+
<VProgress
|
|
462
|
+
:value="60"
|
|
463
|
+
style="--v-progress-color: #7c3aed; --v-progress-track-color: #ede9fe"
|
|
464
|
+
/>
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
| Component | Custom property | Default |
|
|
468
|
+
|---|---|---|
|
|
469
|
+
| `VSpinner` | `--v-spinner-border-width` | `3px` |
|
|
470
|
+
| | `--v-spinner-track-color` | `currentColor` |
|
|
471
|
+
| `VSkeleton` | `--v-skeleton-base-color` | `rgba(148, 163, 184, 0.28)` |
|
|
472
|
+
| | `--v-skeleton-highlight-color` | `rgba(255, 255, 255, 0.55)` |
|
|
473
|
+
| | `--v-skeleton-radius` | `0.25em` (text), `0.5rem` (rect) |
|
|
474
|
+
| | `--v-skeleton-duration` | `1.4s` |
|
|
475
|
+
| `VProgress` | `--v-progress-color` | `#2563eb` |
|
|
476
|
+
| | `--v-progress-track-color` | `#e2e8f0` |
|
|
477
|
+
| | `--v-progress-radius` | `999px` |
|
|
478
|
+
| | `--v-progress-duration` | `1.4s` |
|
|
479
|
+
| `VLoading` | `--v-loading-overlay-background` | `rgba(255, 255, 255, 0.72)` |
|
|
480
|
+
| | `--v-loading-color` | `#334155` |
|
|
481
|
+
| | `--v-loading-z-index` | `10` |
|
|
482
|
+
| | `--v-loading-radius` | `inherit` |
|
|
483
|
+
| | `--v-loading-backdrop-filter` | `none` |
|
|
484
|
+
| `VLoadingButton` | `--v-loading-button-gap` | `0.5rem` |
|
|
485
|
+
|
|
486
|
+
The `color`, `trackColor`, and `height` props on `VProgress`, and the
|
|
487
|
+
`borderRadius` prop on `VSkeleton`, take precedence over the corresponding
|
|
488
|
+
custom properties. Animation keyframes are scoped by the build and do not use
|
|
489
|
+
a shared global name.
|
|
490
|
+
|
|
491
|
+
`VLoading` uses a light translucent overlay by default, which assumes a light
|
|
492
|
+
surface. On a dark surface, override the overlay and the label colour:
|
|
493
|
+
|
|
494
|
+
```vue
|
|
495
|
+
<VLoading
|
|
496
|
+
:loading="isLoading"
|
|
497
|
+
label="Loading"
|
|
498
|
+
style="--v-loading-overlay-background: rgba(15, 10, 30, 0.72); --v-loading-color: #f1eefb"
|
|
499
|
+
>
|
|
500
|
+
<ProductList />
|
|
501
|
+
</VLoading>
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
The loaders are styled entirely through their props and expose no custom
|
|
505
|
+
properties. They inherit `currentColor`, so a colour on an ancestor is enough
|
|
506
|
+
to theme them.
|
|
507
|
+
|
|
508
|
+
## TypeScript
|
|
509
|
+
|
|
510
|
+
Declarations are included via the root `types` export; no separate types
|
|
511
|
+
package or ambient module shim is needed. Public prop and return types are
|
|
512
|
+
exported from the package root:
|
|
513
|
+
|
|
514
|
+
```ts
|
|
515
|
+
import type {
|
|
516
|
+
LoaderVariant,
|
|
517
|
+
LoadingSource,
|
|
518
|
+
NamedLoaderProps,
|
|
519
|
+
UseDelayedLoadingOptions,
|
|
520
|
+
UseDelayedLoadingReturn,
|
|
521
|
+
UseLoadingGroupReturn,
|
|
522
|
+
UseLoadingReturn,
|
|
523
|
+
VLoaderProps,
|
|
524
|
+
VLoadingButtonProps,
|
|
525
|
+
VLoadingProps,
|
|
526
|
+
VProgressProps,
|
|
527
|
+
VSkeletonProps,
|
|
528
|
+
VSpinnerProps,
|
|
529
|
+
} from "veload";
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Use `vue-tsc` (or `nuxt typecheck` in a Nuxt app) to check SFC templates in
|
|
533
|
+
addition to regular TypeScript. Keep Vue within the supported `^3.5.2` peer
|
|
534
|
+
range so the component declarations resolve against compatible Vue types.
|
|
535
|
+
|
|
536
|
+
`execute` preserves the operation's return type:
|
|
537
|
+
|
|
538
|
+
```ts
|
|
539
|
+
interface Product {
|
|
540
|
+
id: number;
|
|
541
|
+
title: string;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const products: Product[] = await execute(async () => {
|
|
545
|
+
const response = await fetch("/api/products");
|
|
546
|
+
return (await response.json()) as Product[];
|
|
547
|
+
});
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
## Loader collection
|
|
551
|
+
|
|
552
|
+
Veloader includes **36 named loader treatments**, selectable through
|
|
553
|
+
`VLoader` or individual named exports, plus its skeleton surface. The
|
|
554
|
+
collection expands the original 32 with four additional treatments:
|
|
555
|
+
|
|
556
|
+
| Component | `VLoader` variant | Marks | Suggested use |
|
|
557
|
+
| --- | --- | --- | --- |
|
|
558
|
+
| `VBreathingLoader` | `breathing` | 2 fixed; ignores `count` | Calm background activity or a waiting state. |
|
|
559
|
+
| `VDualArcLoader` | `dual-arc` | 2 fixed; ignores `count` | Compact circular feedback for refresh or synchronization. |
|
|
560
|
+
| `VDotSweepLoader` | `dot-sweep` | 4 by default; `count` supports 1–12 | Sequential activity beside text or in a list. |
|
|
561
|
+
| `VSlidingBarsLoader` | `sliding-bars` | 4 by default; `count` supports 1–12 | Horizontal feedback for a panel or processing task. |
|
|
562
|
+
|
|
563
|
+
The original 32 treatments and named skeleton surface were implemented in
|
|
564
|
+
Vue with React Loadly as a visual reference. The reference package declares
|
|
565
|
+
MIT metadata but has no tracked license text, so no reference source,
|
|
566
|
+
styles, or assets were reused.
|
|
567
|
+
|
|
568
|
+
All named loaders share `NamedLoaderProps`: `color` (`"currentColor"`), `size`
|
|
569
|
+
(`40`), `duration` (`1000` milliseconds), `count` (`4` where applicable),
|
|
570
|
+
`label` (`"Loading"`), and `decorative` (`false`). `VProgressRingLoader` also
|
|
571
|
+
accepts `value` from `0` to `100`. Import a named loader and use it directly:
|
|
572
|
+
|
|
573
|
+
```vue
|
|
574
|
+
<script setup lang="ts">
|
|
575
|
+
import { VOrbitDotsLoader } from "veload";
|
|
576
|
+
import "veload/style.css";
|
|
577
|
+
</script>
|
|
578
|
+
|
|
579
|
+
<template>
|
|
580
|
+
<VOrbitDotsLoader :size="48" color="#7c3aed" label="Syncing workspace" />
|
|
581
|
+
</template>
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
`count` controls supported repeated-mark treatments, such as `bars`,
|
|
585
|
+
`dots`, `grid` and `orbit-dots`, and is clamped to `1`–`12`. It is not a way
|
|
586
|
+
to change fixed-geometry treatments. Grid, heatmap and squares default to
|
|
587
|
+
four, nine and nine marks respectively; other repeating treatments default
|
|
588
|
+
to four. Row-based repeating treatments grow with the count so their marks
|
|
589
|
+
are not clipped.
|
|
590
|
+
|
|
591
|
+
| Category | Components |
|
|
592
|
+
| --- | --- |
|
|
593
|
+
| Motion | `VBarsLoader`, `VBounceLoader`, `VDotsLoader`, `VDotSweepLoader`, `VEqualizerLoader`, `VSlidingBarsLoader`, `VSpinLoader`, `VSpinDotsLoader`, `VTypingLoader`, `VWaveLoader` |
|
|
594
|
+
| Circular | `VBreathingLoader`, `VDualArcLoader`, `VGradientRingLoader`, `VOrbitDotsLoader`, `VOrbitLoader`, `VProgressRingLoader`, `VPulseLoader`, `VRingLoader`, `VRippleLoader`, `VRotateLoader`, `VSignalLoader`, `VSnakeLoader` |
|
|
595
|
+
| Geometric | `VBlobLoader`, `VCardFlipLoader`, `VElementLoader`, `VFlowLoader`, `VGridLoader`, `VHashtagLoader`, `VHeatmapLoader`, `VLiquidLoader`, `VLogoSpinLoader`, `VMorphLoader`, `VPlaneLoader`, `VSquaresLoader`, `VStairLoader` |
|
|
596
|
+
| Utility | `VFallbackLoader`, `VLoader` (`variant` selects any treatment) |
|
|
597
|
+
|
|
598
|
+
### Skeleton collection
|
|
599
|
+
|
|
600
|
+
`VSkeleton` remains the primitive (`variant`, `width`, `height`, `borderRadius`,
|
|
601
|
+
`animated`, and `label`). It is also exported as `VSkeletonLoader` for catalog
|
|
602
|
+
parity. `VPatternSkeleton` accepts `pattern` (`lines`, `article`, `card`,
|
|
603
|
+
`profile`, `list`, `table`, `media`, `form`, `dashboard`, `feed`, or `product`),
|
|
604
|
+
`rows` (default `3`), `color`, `highlightColor`, `animated` (default `true`),
|
|
605
|
+
and `label`.
|
|
606
|
+
|
|
607
|
+
For `VDashboardSkeleton`, `rows` counts **data rows only**, not the table
|
|
608
|
+
header, statistic tiles, or chart. A fixed header is always rendered, so
|
|
609
|
+
`:rows="1"` means one header plus exactly one data row. `VTableSkeleton`
|
|
610
|
+
uses the same data-row definition but allows its header to be toggled with
|
|
611
|
+
`showHeader`. Row counts are rounded and clamped to `1`–`12`; columns to
|
|
612
|
+
`1`–`6`. This preserves the existing layout and public meaning of `rows`.
|
|
613
|
+
|
|
614
|
+
- Layout helpers: `VSkeletonGroup`, `VSkeletonSwitch`, `VAutoSkeleton`.
|
|
615
|
+
`VAutoSkeleton` is a safe Vue content switcher: it renders a named skeleton
|
|
616
|
+
pattern or `#skeleton` slot while loading and the default slot afterwards. It
|
|
617
|
+
deliberately does not inspect or execute slot components to infer their DOM.
|
|
618
|
+
- Named presets: `VArticleSkeleton`, `VCardSkeleton`, `VDashboardSkeleton`,
|
|
619
|
+
`VFeedSkeleton`, `VFormSkeleton`, `VListSkeleton`, `VMediaCardSkeleton`,
|
|
620
|
+
`VProductCardSkeleton`, `VProfileSkeleton`, and `VTableSkeleton`.
|
|
621
|
+
|
|
622
|
+
Every animation honors `prefers-reduced-motion`; status elements are hidden from
|
|
623
|
+
assistive technology when `decorative` is used or no label is provided.
|
|
624
|
+
|
|
625
|
+
## Limitations
|
|
626
|
+
|
|
627
|
+
- No global loading manager, request deduplication, or automatic HTTP
|
|
628
|
+
integration is provided.
|
|
629
|
+
- `VLoading` positions its overlay absolutely. The wrapper needs content or an
|
|
630
|
+
explicit height to be visible.
|
|
631
|
+
- `VLoading`'s content isolation uses the HTML `inert` attribute. Very old
|
|
632
|
+
browsers may not support it.
|
|
633
|
+
- Progress values only reflect data supplied by the consumer; the library does
|
|
634
|
+
not estimate backend progress.
|
|
635
|
+
- The package has not yet been published. Public installation and release
|
|
636
|
+
availability begin only after npm confirms the first release.
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
## Development
|
|
640
|
+
|
|
641
|
+
### Local maintainer workflow
|
|
642
|
+
|
|
643
|
+
These steps are for authorized repository development before a public
|
|
644
|
+
release, not for consumer installation. Run in `Veloader/pcakage` (the
|
|
645
|
+
directory is actually spelled `pcakage`):
|
|
646
|
+
|
|
647
|
+
```bash
|
|
648
|
+
npm install
|
|
649
|
+
npm run typecheck
|
|
650
|
+
npm test
|
|
651
|
+
npm run build # Vite ESM/CJS build, then declarations
|
|
652
|
+
npm run build:types # declarations only
|
|
653
|
+
npm run check:consumer # pack the library and use it like an application
|
|
654
|
+
npm run check:bundle # measure the tree shaken payload of a consumer
|
|
655
|
+
npm pack --dry-run # inspect package contents
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
The test suite uses Vitest, Vue Test Utils, and jsdom. Timing tests use fake
|
|
659
|
+
timers. `vitest.config.ts` contains the test options and reuses the Vue plugin
|
|
660
|
+
from `vite.config.ts`; the production Vite config imports from `vite`, not
|
|
661
|
+
`vitest/config`. Declaration generation uses `tsconfig.build.json`, which
|
|
662
|
+
excludes the local test suites.
|
|
663
|
+
|
|
664
|
+
Build tooling is declared in this library's own `devDependencies`. Install
|
|
665
|
+
it here (for example, `npm ci --include=dev`) before running the build; an
|
|
666
|
+
installation in the sibling playground is not a replacement. See the
|
|
667
|
+
playground README for its Vercel install order and supported Node version.
|
|
668
|
+
|
|
669
|
+
The build externalizes Vue and emits:
|
|
670
|
+
|
|
671
|
+
- `dist/index.js` (ESM)
|
|
672
|
+
- `dist/index.cjs` (CommonJS)
|
|
673
|
+
- `dist/index.d.ts` (types)
|
|
674
|
+
- `dist/style.css` (extracted styles)
|
|
675
|
+
|
|
676
|
+
`npm run check:consumer` packs the library, extracts the tarball into a
|
|
677
|
+
temporary folder and renders every component on the server with the packed
|
|
678
|
+
package, so the packed artifact is checked rather than only the source.
|
|
679
|
+
This does not publish anything or establish a distribution license.
|
|
680
|
+
|
|
681
|
+
For an authorized external test app, run `npm pack` after building and
|
|
682
|
+
install the resulting `.tgz` using its actual filesystem path from that
|
|
683
|
+
app. A directory link also needs a built `dist` directory and access to a
|
|
684
|
+
compatible Vue peer; it is not a substitute for checking the packed
|
|
685
|
+
artifact. The sibling playground already declares `file:../pcakage`; its
|
|
686
|
+
README documents the repository setup. None of these local steps belong
|
|
687
|
+
in the post-release npm consumer workflow.
|
|
688
|
+
|
|
689
|
+
## Documentation
|
|
690
|
+
|
|
691
|
+
The interactive documentation and playground for Veloader lives in the
|
|
692
|
+
`veloader-playground` workspace. It is a Nuxt 4 application that renders on the
|
|
693
|
+
server and consumes this package through its public entry point:
|
|
694
|
+
|
|
695
|
+
- `/` — landing page with a curated selection
|
|
696
|
+
- `/loaders` and `/loaders/[name]` — all 36 loader treatments
|
|
697
|
+
- `/skeletons` and `/skeletons/[name]` — primitives, patterns and presets
|
|
698
|
+
- `/docs/installation`, `/docs/accessibility`, `/docs/api` — the guides
|
|
699
|
+
|