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.
Files changed (31) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +699 -3
  3. package/dist/components/VLoader/index.vue.d.ts +12 -0
  4. package/dist/components/VLoader/loaders.d.ts +38 -0
  5. package/dist/components/VLoader/types.d.ts +28 -0
  6. package/dist/components/VLoading/index.vue.d.ts +21 -0
  7. package/dist/components/VLoading/types.d.ts +27 -0
  8. package/dist/components/VLoadingButton/index.vue.d.ts +21 -0
  9. package/dist/components/VLoadingButton/types.d.ts +29 -0
  10. package/dist/components/VPatternSkeleton/index.vue.d.ts +18 -0
  11. package/dist/components/VPatternSkeleton/presets.d.ts +26 -0
  12. package/dist/components/VPatternSkeleton/types.d.ts +48 -0
  13. package/dist/components/VProgress/index.vue.d.ts +10 -0
  14. package/dist/components/VProgress/types.d.ts +45 -0
  15. package/dist/components/VSkeleton/index.vue.d.ts +7 -0
  16. package/dist/components/VSkeleton/types.d.ts +37 -0
  17. package/dist/components/VSkeletonGroup/index.vue.d.ts +17 -0
  18. package/dist/components/VSkeletonGroup/types.d.ts +8 -0
  19. package/dist/components/VSkeletonSwitch/index.vue.d.ts +31 -0
  20. package/dist/components/VSkeletonSwitch/types.d.ts +5 -0
  21. package/dist/components/VSpinner/index.vue.d.ts +9 -0
  22. package/dist/components/VSpinner/types.d.ts +29 -0
  23. package/dist/composables/useDelayedLoading.d.ts +32 -0
  24. package/dist/composables/useLoading.d.ts +28 -0
  25. package/dist/composables/useLoadingGroup.d.ts +25 -0
  26. package/dist/index.cjs +1 -0
  27. package/dist/index.d.ts +24 -0
  28. package/dist/index.js +851 -0
  29. package/dist/style.css +2 -0
  30. package/dist/utils/css.d.ts +7 -0
  31. package/package.json +56 -6
package/README.md CHANGED
@@ -1,3 +1,699 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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
+