ng-hub-ui-loading 22.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Carlos Morcillo FernΓ‘ndez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALING IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,555 @@
1
+ # ng-hub-ui-loading
2
+
3
+ [EspaΓ±ol](./README.es.md) | **English**
4
+
5
+ [![NPM Version](https://img.shields.io/npm/v/ng-hub-ui-loading.svg)](https://www.npmjs.com/package/ng-hub-ui-loading)
6
+ [![Angular](https://img.shields.io/badge/Angular-22%2B-red.svg)](https://angular.dev)
7
+ [![License](https://img.shields.io/npm/l/ng-hub-ui-loading.svg)](LICENSE)
8
+
9
+ Standalone loading block for Angular 22+ β€” an inline indicator, an overlay pinned over the container that is busy, or a fullscreen curtain, from a single `<hub-loading>` element. Five pure-CSS indicators, an optional logo or image instead of them, an optional message, and a counter-based `HubLoadingService` for the app-wide fullscreen case. Zero external dependencies; every colour and dimension is a `--hub-loading-*` CSS custom property.
10
+
11
+ ## Documentation and Live Examples
12
+
13
+ This package is part of [Hub UI](https://hubui.dev/en/), a collection of Angular component libraries for standalone apps.
14
+
15
+ - Docs: https://hubui.dev/en/loading/overview/
16
+ - Live examples: https://hubui.dev/en/loading/examples/
17
+ - Hub UI: https://hubui.dev/en/
18
+
19
+ ## 🧩 Library Family `ng-hub-ui`
20
+
21
+ This library is part of the **ng-hub-ui** ecosystem:
22
+
23
+ - [**ng-hub-ui-action-sheet**](https://www.npmjs.com/package/ng-hub-ui-action-sheet)
24
+ - [**ng-hub-ui-avatar**](https://www.npmjs.com/package/ng-hub-ui-avatar)
25
+ - [**ng-hub-ui-badges**](https://www.npmjs.com/package/ng-hub-ui-badges)
26
+ - [**ng-hub-ui-board**](https://www.npmjs.com/package/ng-hub-ui-board)
27
+ - [**ng-hub-ui-breadcrumbs**](https://www.npmjs.com/package/ng-hub-ui-breadcrumbs)
28
+ - [**ng-hub-ui-buttons**](https://www.npmjs.com/package/ng-hub-ui-buttons)
29
+ - [**ng-hub-ui-calendar**](https://www.npmjs.com/package/ng-hub-ui-calendar)
30
+ - [**ng-hub-ui-ds**](https://www.npmjs.com/package/ng-hub-ui-ds)
31
+ - [**ng-hub-ui-forms**](https://www.npmjs.com/package/ng-hub-ui-forms)
32
+ - [**ng-hub-ui-history**](https://www.npmjs.com/package/ng-hub-ui-history)
33
+ - [**ng-hub-ui-icons**](https://www.npmjs.com/package/ng-hub-ui-icons)
34
+ - [**ng-hub-ui-loading**](https://www.npmjs.com/package/ng-hub-ui-loading) ← You are here
35
+ - [**ng-hub-ui-metrics**](https://www.npmjs.com/package/ng-hub-ui-metrics)
36
+ - [**ng-hub-ui-milestones**](https://www.npmjs.com/package/ng-hub-ui-milestones)
37
+ - [**ng-hub-ui-modal**](https://www.npmjs.com/package/ng-hub-ui-modal)
38
+ - [**ng-hub-ui-nav**](https://www.npmjs.com/package/ng-hub-ui-nav)
39
+ - [**ng-hub-ui-paginable**](https://www.npmjs.com/package/ng-hub-ui-paginable)
40
+ - [**ng-hub-ui-panels**](https://www.npmjs.com/package/ng-hub-ui-panels)
41
+ - [**ng-hub-ui-portal**](https://www.npmjs.com/package/ng-hub-ui-portal)
42
+ - [**ng-hub-ui-signature**](https://www.npmjs.com/package/ng-hub-ui-signature)
43
+ - [**ng-hub-ui-skeleton**](https://www.npmjs.com/package/ng-hub-ui-skeleton)
44
+ - [**ng-hub-ui-sortable**](https://www.npmjs.com/package/ng-hub-ui-sortable)
45
+ - [**ng-hub-ui-stepper**](https://www.npmjs.com/package/ng-hub-ui-stepper)
46
+ - [**ng-hub-ui-toast**](https://www.npmjs.com/package/ng-hub-ui-toast)
47
+ - [**ng-hub-ui-utils**](https://www.npmjs.com/package/ng-hub-ui-utils)
48
+
49
+ ## πŸ“‘ Table of Contents
50
+
51
+ - [πŸ“¦ Description](#-description)
52
+ - [✨ Features](#-features)
53
+ - [πŸš€ Quick Start](#-quick-start)
54
+ - [🎯 Modes](#-modes)
55
+ - [πŸŽ›οΈ Variants](#️-variants)
56
+ - [πŸ–ΌοΈ Image and Branding](#️-image-and-branding)
57
+ - [🧰 Programmatic API](#-programmatic-api)
58
+ - [πŸ“– API Reference](#-api-reference)
59
+ - [🎨 Styling / CSS Variables](#-styling--css-variables)
60
+ - [β™Ώ Accessibility](#-accessibility)
61
+ - [πŸ–₯️ Server-Side Rendering](#️-server-side-rendering)
62
+ - [πŸ“¦ Peer Dependencies](#-peer-dependencies)
63
+ - [πŸ“Š Changelog](#-changelog)
64
+ - [🀝 Contribution](#-contribution)
65
+ - [β˜• Support](#-support)
66
+ - [πŸ“„ License](#-license)
67
+
68
+ ## πŸ“¦ Description
69
+
70
+ `ng-hub-ui-loading` fills the gap between the family's other "something is happening"
71
+ primitives and the spinner that used to be trapped inside a button: a standalone block
72
+ that shows an activity indicator, an optional image or logo and an optional message,
73
+ and that can render in flow, over its own container, or over the whole viewport.
74
+
75
+ ### When to reach for which library
76
+
77
+ | Package | Use it when |
78
+ | --- | --- |
79
+ | **`ng-hub-ui-loading`** | You cannot say how far along the work is, and the shape of the result does not matter yet. An activity indicator β€” in place, over the busy container, or over the whole app. |
80
+ | [`ng-hub-ui-skeleton`](https://www.npmjs.com/package/ng-hub-ui-skeleton) | You already know the shape of what is coming and want the layout to hold its place β€” structural shimmer placeholders instead of a spinner. |
81
+ | [`ng-hub-ui-metrics`](https://www.npmjs.com/package/ng-hub-ui-metrics) | You know the progress figure β€” a determinate progress bar, meter or ring that reports a value. |
82
+
83
+ The three compose: a skeleton for the list that is arriving, a `<hub-loading mode="overlay">`
84
+ over the panel being refreshed, and a `<hub-progress>` for the upload that reports bytes.
85
+
86
+ ## ✨ Features
87
+
88
+ - **Three modes in one component** β€” `inline`, container `overlay` and viewport `fullscreen`.
89
+ - **Five pure-CSS indicators** β€” `spinner`, `dots`, `bars`, `pulse` and `ring`; no JavaScript animation loop, no SVG sprite, no icon font.
90
+ - **Image / logo support** β€” swap the indicator for your own brand asset and animate it with `spin` or `pulse`.
91
+ - **Optional message and projected content** β€” a caption under the indicator plus an `<ng-content>` slot for anything else.
92
+ - **Programmatic overlays** β€” `HubLoadingService` mounts a fullscreen `<hub-loading>` on demand, with reference-counted `show()` / `hide()` so concurrent tasks cannot dismiss each other's overlay.
93
+ - **Application-wide defaults** β€” `provideHubLoading()` re-bases every input's default, for the service *and* for every `<hub-loading>` in a template, without touching a single markup file.
94
+ - **Any accent colour** β€” `color` accepts a semantic design-system name, a hex value, `oklch()` or a `var(...)` reference, resolved through `resolveHubAccent()` from `ng-hub-ui-utils`.
95
+ - **CSS-variable theming** β€” every colour, dimension and speed is a `--hub-loading-*` custom property, with a `hub-loading-theme()` Sass mixin for one-call re-skinning.
96
+ - **Accessible by default** β€” `role="status"`, `aria-live="polite"` and `aria-busy="true"`, with a configurable `ariaLabel` and a `prefers-reduced-motion` treatment that calms the motion instead of freezing it.
97
+ - **Standalone, `OnPush`, signal inputs** β€” and SSR-safe: on the server the counter still runs, only the DOM mount is skipped.
98
+
99
+ ## πŸš€ Quick Start
100
+
101
+ ### 1. Install
102
+
103
+ ```bash
104
+ npm install ng-hub-ui-loading ng-hub-ui-utils
105
+ ```
106
+
107
+ > **Theming (recommended):** install the shared design tokens so the loading block β€”
108
+ > and every other ng-hub-ui library β€” reads the same palette and dark-mode colours:
109
+ >
110
+ > ```bash
111
+ > npm install ng-hub-ui-ds
112
+ > ```
113
+ >
114
+ > ```css
115
+ > @import 'ng-hub-ui-ds/styles/tokens/hub-tokens.css';
116
+ > ```
117
+ >
118
+ > It is an **optional** peer dependency: the component ships sensible CSS fallbacks
119
+ > and works without it.
120
+
121
+ ### 2. Import the standalone component
122
+
123
+ ```typescript
124
+ import { HubLoadingComponent } from 'ng-hub-ui-loading';
125
+
126
+ @Component({
127
+ standalone: true,
128
+ imports: [HubLoadingComponent],
129
+ template: `
130
+ @if (isLoading()) {
131
+ <hub-loading message="Loading results…" />
132
+ }
133
+ `
134
+ })
135
+ export class ResultsComponent {
136
+ readonly isLoading = signal(true);
137
+ }
138
+ ```
139
+
140
+ ## 🎯 Modes
141
+
142
+ The `mode` input decides where the block paints. Everything else β€” variant, image,
143
+ message, size, colour β€” works identically in all three.
144
+
145
+ ### `inline` (default)
146
+
147
+ Renders in the document flow, like any other block. Use it inside the empty area it
148
+ is standing in for: a panel body, a table placeholder, a card that has not resolved yet.
149
+
150
+ ```html
151
+ <hub-loading message="Fetching invoices…" />
152
+ ```
153
+
154
+ ### `overlay`
155
+
156
+ Absolutely positioned over its parent, so the stale content stays visible underneath
157
+ while the refresh runs.
158
+
159
+ > The parent must establish a positioning context β€” give it `position: relative`.
160
+
161
+ ```html
162
+ <section class="panel" style="position: relative">
163
+ <article>…already rendered content…</article>
164
+
165
+ @if (refreshing()) {
166
+ <hub-loading mode="overlay" variant="ring" message="Refreshing…" />
167
+ }
168
+ </section>
169
+ ```
170
+
171
+ ### `fullscreen`
172
+
173
+ Fixed to the viewport, covering the application. Declare it in a template when the
174
+ component owns the state, or let [`HubLoadingService`](#-programmatic-api) mount it for you.
175
+
176
+ ```html
177
+ @if (booting()) {
178
+ <hub-loading mode="fullscreen" variant="pulse" message="Starting up…" />
179
+ }
180
+ ```
181
+
182
+ ### Backdrop
183
+
184
+ `backdrop` paints the translucent layer behind the indicator. It applies to `overlay`
185
+ and `fullscreen` only β€” an inline block has nothing to cover β€” and is on by default.
186
+
187
+ ```html
188
+ <hub-loading mode="overlay" [backdrop]="false" />
189
+ ```
190
+
191
+ ## πŸŽ›οΈ Variants
192
+
193
+ Five indicators, all drawn with CSS. Pick with the `variant` input.
194
+
195
+ | Variant | Shape |
196
+ | --- | --- |
197
+ | `spinner` | Rotating arc (the default). |
198
+ | `dots` | Three dots pulsing in sequence. |
199
+ | `bars` | Bars rising and falling. |
200
+ | `pulse` | A single expanding, fading disc. |
201
+ | `ring` | A full ring with a travelling highlight. |
202
+
203
+ ```html
204
+ <hub-loading variant="dots" />
205
+ <hub-loading variant="bars" size="lg" color="success" />
206
+ <hub-loading variant="ring" color="#7c3aed" />
207
+ ```
208
+
209
+ `size` picks between `sm`, `md` (default) and `lg`. Each step retunes the tokens rather
210
+ than hard-coding dimensions β€” `--hub-loading-size` (`1.5rem` / `2.5rem` / `4rem`), the
211
+ indicator `--hub-loading-thickness` and the message `--hub-loading-font-size` β€” so any
212
+ value outside those three steps is one CSS custom property away:
213
+
214
+ ```css
215
+ .hero-loading {
216
+ --hub-loading-size: 6rem;
217
+ }
218
+ ```
219
+
220
+ `color` accepts a semantic accent name (`primary`, `success`, `brand`…), a literal colour
221
+ (`#7c3aed`, `rgb(...)`, `oklch(...)`) or a `var(...)` reference. Barewords resolve to the
222
+ design-system token `var(--hub-sys-color-<name>, <name>)`, so unregistered names and CSS
223
+ named colours still paint.
224
+
225
+ ## πŸ–ΌοΈ Image and Branding
226
+
227
+ Set `image` to a URL or a data URI and it replaces the built-in indicator β€” the usual
228
+ case being a product logo on the boot screen. `imageAnimation` gives it motion.
229
+
230
+ ```html
231
+ <hub-loading mode="fullscreen" image="/assets/logo.svg" imageAnimation="pulse" message="Preparing your workspace…" />
232
+ ```
233
+
234
+ | `imageAnimation` | Effect |
235
+ | --- | --- |
236
+ | `none` | Static image (the default). |
237
+ | `spin` | Continuous rotation. |
238
+ | `pulse` | Rhythmic scale/opacity beat. |
239
+
240
+ Size the asset with `--hub-loading-image-size`, and use `--hub-loading-speed` to keep the
241
+ animation in step with the rest of your brand's motion.
242
+
243
+ Anything projected into the component renders below the message, which is where extra
244
+ context or an escape hatch belongs:
245
+
246
+ ```html
247
+ <hub-loading mode="fullscreen" message="Importing 12,480 rows…">
248
+ <button type="button" (click)="cancel()">Cancel import</button>
249
+ </hub-loading>
250
+ ```
251
+
252
+ ## 🧰 Programmatic API
253
+
254
+ `HubLoadingService` covers the case the declarative component cannot: work that starts
255
+ in a service, a route guard or an effect, where no template owns the flag. It mounts a
256
+ single fullscreen `<hub-loading>` on `document.body` the first time it is needed.
257
+
258
+ ```typescript
259
+ import { inject } from '@angular/core';
260
+ import { HubLoadingService } from 'ng-hub-ui-loading';
261
+
262
+ @Injectable({ providedIn: 'root' })
263
+ export class ReportService {
264
+ private readonly loading = inject(HubLoadingService);
265
+
266
+ async export(): Promise<void> {
267
+ this.loading.show({ message: 'Building the report…' });
268
+ try {
269
+ await this.buildReport();
270
+ } finally {
271
+ this.loading.hide();
272
+ }
273
+ }
274
+ }
275
+ ```
276
+
277
+ ### Reference counting
278
+
279
+ `show()` increments an internal counter and `hide()` decrements it; the overlay is
280
+ destroyed only when the counter reaches zero. Two concurrent tasks can therefore each
281
+ call `show()` / `hide()` without the first one to finish tearing down the overlay the
282
+ second still needs. `hideAll()` forces the counter to zero β€” the right call from a
283
+ global error handler or a route change, where the pending `hide()` calls may never arrive.
284
+
285
+ ```typescript
286
+ this.loading.show(); // counter 1 β€” overlay appears
287
+ this.loading.show(); // counter 2 β€” same overlay
288
+ this.loading.hide(); // counter 1 β€” overlay stays
289
+ this.loading.hide(); // counter 0 β€” overlay is destroyed
290
+ ```
291
+
292
+ The counter is clamped at zero, so a stray extra `hide()` is harmless β€” it cannot push
293
+ the count negative and turn a later `show()` into a no-op.
294
+
295
+ ### Updating a live overlay
296
+
297
+ `update()` changes the visible overlay in place β€” typically the message, as a long task
298
+ moves through its phases.
299
+
300
+ ```typescript
301
+ this.loading.show({ variant: 'ring', message: 'Connecting…' });
302
+ this.loading.update({ message: 'Downloading…' });
303
+ this.loading.update({ message: 'Almost there…' });
304
+ this.loading.hide();
305
+ ```
306
+
307
+ Options accumulate rather than replace, and the two empty values mean different things:
308
+ an **omitted** key (or `undefined`) leaves the current value alone, while `null` clears it.
309
+ `{ message: null }` therefore removes the caption; `{ message: undefined }` keeps it.
310
+ The accumulated options are dropped once the counter reaches zero, so the next overlay
311
+ never inherits a stale message from a finished operation.
312
+
313
+ `isLoading` is a signal, so the rest of the application can react to the same state:
314
+
315
+ ```typescript
316
+ readonly busy = this.loading.isLoading; // Signal<boolean>
317
+ ```
318
+
319
+ ### Defaults with `provideHubLoading()`
320
+
321
+ Register the provider once to re-base the defaults for the whole application β€” the brand
322
+ image, the preferred variant, a translated label β€” instead of repeating them at each call
323
+ site. It reaches **both** consumers: the service's overlays *and* every `<hub-loading>`
324
+ written in a template, because each component input falls back to the same configuration.
325
+ A per-instance binding still wins locally, and individual `show()` / `update()` options are
326
+ merged on top.
327
+
328
+ ```typescript
329
+ // app.config.ts
330
+ import { ApplicationConfig } from '@angular/core';
331
+ import { provideHubLoading } from 'ng-hub-ui-loading';
332
+
333
+ export const appConfig: ApplicationConfig = {
334
+ providers: [
335
+ provideHubLoading({
336
+ variant: 'ring',
337
+ color: 'brand',
338
+ backdrop: true,
339
+ ariaLabel: 'Loading, please wait'
340
+ })
341
+ ]
342
+ };
343
+ ```
344
+
345
+ The configuration is backed by the `HUB_LOADING_CONFIG` injection token, which you can
346
+ provide directly if you need to compute it from another dependency. Its unconfigured
347
+ value is exported as `HUB_LOADING_DEFAULT_CONFIG` β€” the same values listed as each input's
348
+ default below.
349
+
350
+ ## πŸ“– API Reference
351
+
352
+ ### `HubLoadingComponent`
353
+
354
+ Selector: `hub-loading`. Standalone, `OnPush`, signal inputs.
355
+
356
+ | Input | Type | Default | Description |
357
+ | --- | --- | --- | --- |
358
+ | `mode` | `'inline' \| 'overlay' \| 'fullscreen'` | `'inline'` | Where the block paints. `overlay` is absolutely positioned over the parent (which needs `position: relative`); `fullscreen` is fixed to the viewport. |
359
+ | `variant` | `'spinner' \| 'dots' \| 'bars' \| 'pulse' \| 'ring'` | `'spinner'` | Which pure-CSS indicator to draw. Ignored when `image` is set. |
360
+ | `image` | `string \| null` | `null` | URL or data URI rendered instead of the built-in indicator. |
361
+ | `imageAnimation` | `'none' \| 'spin' \| 'pulse'` | `'none'` | Animation applied to `image`. |
362
+ | `message` | `string \| null` | `null` | Text rendered under the indicator. |
363
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Indicator scale; maps onto `--hub-loading-size`, which always overrides it. |
364
+ | `color` | `string \| null` | `null` | Accent colour: semantic name, hex, `rgb()`, `oklch()` or `var(...)`. Resolved with `resolveHubAccent()`. |
365
+ | `backdrop` | `boolean` | `true` | Translucent layer behind the indicator. Applies to `overlay` and `fullscreen` only. Read with `booleanAttribute`, so the bare `backdrop` attribute also works. |
366
+ | `ariaLabel` | `string` | `'Loading'` | Accessible name of the status region. |
367
+
368
+ This component has no outputs. Content projected into it renders below the message.
369
+
370
+ > Every default except `mode`'s comes from the injected `HUB_LOADING_CONFIG`. The values in
371
+ > the table are the unconfigured ones; `provideHubLoading()` re-bases them application-wide.
372
+
373
+ ### `HubLoadingService`
374
+
375
+ Injectable (`providedIn: 'root'`). Drives a single fullscreen overlay attached to `document.body`.
376
+
377
+ | Member | Signature | Description |
378
+ | --- | --- | --- |
379
+ | `show` | `(options?: HubLoadingOptions) => void` | Increments the counter and creates the overlay if it is not mounted yet. |
380
+ | `hide` | `() => void` | Decrements the counter; destroys the overlay when it reaches zero. |
381
+ | `hideAll` | `() => void` | Forces the counter to zero and destroys the overlay. |
382
+ | `update` | `(options: HubLoadingOptions) => void` | Applies new options to the visible overlay. |
383
+ | `isLoading` | `Signal<boolean>` | Whether an overlay is currently mounted. |
384
+
385
+ ### `provideHubLoading(config?)`
386
+
387
+ Environment provider that registers the defaults every indicator starts from, through the
388
+ `HUB_LOADING_CONFIG` injection token. Omitted keys keep their `HUB_LOADING_DEFAULT_CONFIG`
389
+ value.
390
+
391
+ ```typescript
392
+ function provideHubLoading(config?: Partial<HubLoadingConfig>): EnvironmentProviders;
393
+ ```
394
+
395
+ ### `HubLoadingOptions`
396
+
397
+ The visual options accepted by `show()` and `update()`, and by `provideHubLoading()` as
398
+ application-wide defaults. They mirror the component inputs, minus `mode` β€” a programmatic
399
+ overlay is always fullscreen.
400
+
401
+ | Option | Type | Description |
402
+ | --- | --- | --- |
403
+ | `variant` | `'spinner' \| 'dots' \| 'bars' \| 'pulse' \| 'ring'` | Indicator to draw. |
404
+ | `image` | `string \| null` | Image or logo replacing the indicator. |
405
+ | `imageAnimation` | `'none' \| 'spin' \| 'pulse'` | Animation applied to `image`. |
406
+ | `message` | `string \| null` | Text under the indicator. |
407
+ | `size` | `'sm' \| 'md' \| 'lg'` | Indicator scale. |
408
+ | `color` | `string \| null` | Accent colour. |
409
+ | `backdrop` | `boolean` | Translucent layer behind the indicator. |
410
+ | `ariaLabel` | `string` | Accessible name of the status region. |
411
+
412
+ `HubLoadingConfig` is the same shape with every member required β€” it is what the injection
413
+ token holds once resolved.
414
+
415
+ ### Exported types
416
+
417
+ ```typescript
418
+ type HubLoadingMode = 'inline' | 'overlay' | 'fullscreen';
419
+ type HubLoadingVariant = 'spinner' | 'dots' | 'bars' | 'pulse' | 'ring';
420
+ type HubLoadingSize = 'sm' | 'md' | 'lg';
421
+ type HubLoadingImageAnimation = 'none' | 'spin' | 'pulse';
422
+ ```
423
+
424
+ Also exported: `HubLoadingOptions`, `HubLoadingConfig`, `HUB_LOADING_CONFIG` and
425
+ `HUB_LOADING_DEFAULT_CONFIG`.
426
+
427
+ ## 🎨 Styling / CSS Variables
428
+
429
+ The component declares its token defaults on `:where(.hub-loading)` β€” zero specificity, so
430
+ any consumer rule wins β€” and each one climbs the family's ladder: the semantic `--hub-sys-*`
431
+ layer first, the `--hub-ref-*` primitive next, a literal last. That is why the block already
432
+ matches your theme, and its dark mode, before you override anything. Values with no honest
433
+ counterpart in the design system (an indicator's diameter, a loop period, a blur radius)
434
+ carry a literal rather than borrowing a `sys` token that means something else.
435
+
436
+ Styles are deliberately unencapsulated, so a global stylesheet can retheme the indicator β€”
437
+ and so the service-mounted overlay, created outside any component's style scope, is painted.
438
+
439
+ | Variable | Default | Description |
440
+ | --- | --- | --- |
441
+ | `--hub-loading-accent` | `var(--hub-sys-color-primary, #0d6efd)` | Indicator colour. What the `color` input writes into. |
442
+ | `--hub-loading-size` | `2.5rem` | Indicator box size. What the `size` input maps onto. |
443
+ | `--hub-loading-thickness` | `calc(var(--hub-ref-border-width, 1px) * 3)` | Stroke width of the `spinner` and `ring` indicators. |
444
+ | `--hub-loading-speed` | `0.9s` | Duration of one animation cycle, for the indicators and the image animations. |
445
+ | `--hub-loading-gap` | `var(--hub-sys-gap-2, var(--hub-ref-space-2, 0.5rem))` | Space between indicator, message and projected content. |
446
+ | `--hub-loading-text-color` | `var(--hub-sys-text-primary, var(--hub-ref-color-gray-900, #212529))` | Message colour. |
447
+ | `--hub-loading-font-size` | `var(--hub-ref-font-size-sm, 0.875rem)` | Message font size. |
448
+ | `--hub-loading-backdrop-bg` | `color-mix(in srgb, var(--hub-sys-surface-page, #fff) 72%, transparent)` | Backdrop background in `overlay` / `fullscreen`. Follows the theme's own page surface, so the scrim is a white veil on light themes and a dark one on dark themes. |
449
+ | `--hub-loading-backdrop-blur` | `2px` | Backdrop blur radius. |
450
+ | `--hub-loading-z-index` | `var(--hub-sys-zindex-modal, 1055)` | Stack order of the `fullscreen` layer. |
451
+ | `--hub-loading-image-size` | `var(--hub-loading-size)` | Rendered size of the `image` asset β€” it follows the indicator size until you say otherwise. |
452
+
453
+ ```css
454
+ hub-loading {
455
+ --hub-loading-accent: var(--hub-sys-color-brand);
456
+ --hub-loading-size: 3rem;
457
+ --hub-loading-speed: 1.2s;
458
+ --hub-loading-backdrop-blur: 4px;
459
+ }
460
+ ```
461
+
462
+ ### The `hub-loading-theme()` Sass mixin
463
+
464
+ For Sass projects, `hub-loading-theme()` re-skins the block in a single include. Every
465
+ parameter is optional and defaults to `null`, so only the ones you pass are emitted as
466
+ `--hub-loading-*` overrides β€” the rest keep the component defaults. It is token-based and
467
+ self-contained (no Bootstrap dependency).
468
+
469
+ ```scss
470
+ @use 'ng-hub-ui-loading/styles' as hub;
471
+
472
+ .app-shell {
473
+ @include hub.hub-loading-theme(
474
+ $accent: var(--hub-sys-color-brand),
475
+ $size: 3.5rem,
476
+ $speed: 1.2s,
477
+ $backdrop-bg: rgba(15, 23, 42, 0.72),
478
+ $backdrop-blur: 4px
479
+ );
480
+ }
481
+ ```
482
+
483
+ Available parameters: `$accent`, `$size`, `$thickness`, `$speed`, `$gap`, `$text-color`,
484
+ `$font-size`, `$backdrop-bg`, `$backdrop-blur`, `$z-index`, `$image-size`.
485
+
486
+ ### BEM classes
487
+
488
+ The internal structure is stable and addressable, for the cases a token cannot reach:
489
+
490
+ | Class | Element |
491
+ | --- | --- |
492
+ | `.hub-loading` | Host block. |
493
+ | `.hub-loading--inline` Β· `--overlay` Β· `--fullscreen` | Mode modifiers. |
494
+ | `.hub-loading--sm` Β· `--md` Β· `--lg` | Size modifiers; each retunes the size, thickness and font-size tokens. |
495
+ | `.hub-loading--backdrop` | Present only when the scrim is painted (never in `inline` mode). |
496
+ | `.hub-loading__indicator` | The pure-CSS indicator, plus a `--spinner` / `--dots` / `--bars` / `--pulse` / `--ring` modifier. |
497
+ | `.hub-loading__dot` Β· `.hub-loading__bar` | The individual parts of the `dots` and `bars` indicators. |
498
+ | `.hub-loading__image` | The `image` asset, plus `--spin` / `--pulse` when animated. |
499
+ | `.hub-loading__message` | The message text. |
500
+
501
+ ## β™Ώ Accessibility
502
+
503
+ - The block is a **status region**: `role="status"`, `aria-live="polite"` and
504
+ `aria-busy="true"`. A polite live region is announced at the next natural pause, so
505
+ starting a load never interrupts what the user is reading.
506
+ - `ariaLabel` (default `'Loading'`) names that region. Give it something specific when the
507
+ page can have several β€” `"Loading invoices"` beats a second generic "Loading".
508
+ - Because `message` lives inside the live region, changing it β€” including through
509
+ `HubLoadingService.update()` β€” is announced, which is what makes a phased task readable
510
+ without sight.
511
+ - The indicator and the `image` are decorative (`aria-hidden`, empty `alt`), so nothing is
512
+ announced twice. What carries the meaning is `ariaLabel` and the `message`.
513
+ - Render the block only while the work is actually running. A permanently mounted
514
+ `aria-busy="true"` region tells assistive technology the app is forever loading.
515
+ - Under `prefers-reduced-motion: reduce` the animation is **calmed, not frozen** β€” a frozen
516
+ loader reads as a hung UI. The cycle slows to `2.4s` and every rotation or scale is
517
+ swapped for a plain fade, so nothing spins or jumps.
518
+
519
+ ## πŸ–₯️ Server-Side Rendering
520
+
521
+ - `<hub-loading>` is declarative markup and CSS with no browser API in the render path,
522
+ so it renders on the server like any other component.
523
+ - `HubLoadingService` is safe to call during server rendering: `show()`, `update()`,
524
+ `hide()` and `hideAll()` need no platform guard of their own. Only the DOM mount is
525
+ skipped β€” the reference counter still runs, so `isLoading` stays truthful and hydration
526
+ finds no orphan overlay markup.
527
+
528
+ ## πŸ“¦ Peer Dependencies
529
+
530
+ ```json
531
+ {
532
+ "@angular/common": ">=21.0.0",
533
+ "@angular/core": ">=21.0.0",
534
+ "ng-hub-ui-utils": ">=22.8.0"
535
+ }
536
+ ```
537
+
538
+ ## πŸ“Š Changelog
539
+
540
+ See [CHANGELOG.md](./CHANGELOG.md) for the full version history, and
541
+ [BREAKING_CHANGES.md](./BREAKING_CHANGES.md) for migration notes.
542
+
543
+ ## 🀝 Contribution
544
+
545
+ Contributions are welcome. Please open an issue to discuss substantial changes before
546
+ submitting a pull request, and make sure to document every library change in `CHANGELOG.md`.
547
+
548
+ ## β˜• Support
549
+
550
+ - **Issues**: [GitHub Issues](https://github.com/carlos-morcillo/ng-hub-ui/issues)
551
+ - **Author**: [Carlos Morcillo](https://www.carlosmorcillo.com)
552
+
553
+ ## πŸ“„ License
554
+
555
+ MIT Β© [Carlos Morcillo](https://www.carlosmorcillo.com)