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 +21 -0
- package/README.md +555 -0
- package/fesm2022/ng-hub-ui-loading.mjs +312 -0
- package/fesm2022/ng-hub-ui-loading.mjs.map +1 -0
- package/package.json +48 -0
- package/styles/_index.scss +1 -0
- package/styles/mixins/_loading-theme.scss +79 -0
- package/types/ng-hub-ui-loading.d.ts +252 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, makeEnvironmentProviders, inject, input, booleanAttribute, computed, ViewEncapsulation, ChangeDetectionStrategy, Component, ApplicationRef, PLATFORM_ID, signal, createComponent, Injectable } from '@angular/core';
|
|
3
|
+
import { resolveHubAccent } from 'ng-hub-ui-utils';
|
|
4
|
+
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Neutral defaults applied when an application provides no configuration.
|
|
8
|
+
*
|
|
9
|
+
* These are the values documented as each input's default, so overriding the
|
|
10
|
+
* token silently re-bases the whole application without touching a template.
|
|
11
|
+
*/
|
|
12
|
+
const HUB_LOADING_DEFAULT_CONFIG = {
|
|
13
|
+
message: null,
|
|
14
|
+
variant: 'spinner',
|
|
15
|
+
image: null,
|
|
16
|
+
imageAnimation: 'none',
|
|
17
|
+
size: 'md',
|
|
18
|
+
color: null,
|
|
19
|
+
backdrop: true,
|
|
20
|
+
ariaLabel: 'Loading'
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Resolved defaults shared by `<hub-loading>` and `HubLoadingService`.
|
|
24
|
+
*
|
|
25
|
+
* Declared with a root factory so the token is always injectable, even when the
|
|
26
|
+
* application never calls {@link provideHubLoading}.
|
|
27
|
+
*/
|
|
28
|
+
const HUB_LOADING_CONFIG = new InjectionToken('HUB_LOADING_CONFIG', {
|
|
29
|
+
providedIn: 'root',
|
|
30
|
+
factory: () => HUB_LOADING_DEFAULT_CONFIG
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* Registers application-wide loading defaults — typically the brand image, the
|
|
34
|
+
* preferred variant and a translated label — so individual call sites stay bare.
|
|
35
|
+
*
|
|
36
|
+
* @param config - Values overriding {@link HUB_LOADING_DEFAULT_CONFIG}; omitted keys keep their default.
|
|
37
|
+
* @returns Environment providers for the application bootstrap.
|
|
38
|
+
*/
|
|
39
|
+
function provideHubLoading(config = {}) {
|
|
40
|
+
return makeEnvironmentProviders([
|
|
41
|
+
{
|
|
42
|
+
provide: HUB_LOADING_CONFIG,
|
|
43
|
+
useValue: { ...HUB_LOADING_DEFAULT_CONFIG, ...config }
|
|
44
|
+
}
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Activity indicator rendered inline, over its container or over the viewport.
|
|
50
|
+
*
|
|
51
|
+
* Every input defaults to the injected `HUB_LOADING_CONFIG`, so `provideHubLoading()`
|
|
52
|
+
* re-bases an entire application (brand image, variant, translated label) without
|
|
53
|
+
* touching a single template, while a per-instance binding still wins locally.
|
|
54
|
+
*
|
|
55
|
+
* Styles are unencapsulated on purpose: the host carries the `hub-loading` class and
|
|
56
|
+
* the token block, so consumers can retheme the indicator from a global stylesheet —
|
|
57
|
+
* and so the service-mounted overlay, created outside any component's style scope,
|
|
58
|
+
* is still painted.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```html
|
|
62
|
+
* <hub-loading variant="dots" message="Loading orders…" />
|
|
63
|
+
*
|
|
64
|
+
* <div style="position: relative">
|
|
65
|
+
* <hub-loading mode="overlay" color="primary" />
|
|
66
|
+
* </div>
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
class HubLoadingComponent {
|
|
70
|
+
/** Application-wide defaults; also the source of every input's default value. */
|
|
71
|
+
config = inject(HUB_LOADING_CONFIG);
|
|
72
|
+
/**
|
|
73
|
+
* Placement of the indicator. `overlay` needs a positioned ancestor to cover;
|
|
74
|
+
* `fullscreen` is fixed to the viewport and layered at `--hub-loading-z-index`.
|
|
75
|
+
*/
|
|
76
|
+
mode = input('inline', /* @ts-ignore */
|
|
77
|
+
...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
78
|
+
/** Built-in CSS indicator rendered when no {@link image} is supplied. */
|
|
79
|
+
variant = input(this.config.variant, /* @ts-ignore */
|
|
80
|
+
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
81
|
+
/** URL or data URI shown instead of the built-in indicator. */
|
|
82
|
+
image = input(this.config.image, /* @ts-ignore */
|
|
83
|
+
...(ngDevMode ? [{ debugName: "image" }] : /* istanbul ignore next */ []));
|
|
84
|
+
/** Motion applied to {@link image}; inert while no image is set. */
|
|
85
|
+
imageAnimation = input(this.config.imageAnimation, /* @ts-ignore */
|
|
86
|
+
...(ngDevMode ? [{ debugName: "imageAnimation" }] : /* istanbul ignore next */ []));
|
|
87
|
+
/** Text rendered below the indicator. */
|
|
88
|
+
message = input(this.config.message, /* @ts-ignore */
|
|
89
|
+
...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
|
|
90
|
+
/** Size step feeding `--hub-loading-size`; the token remains overridable on its own. */
|
|
91
|
+
size = input(this.config.size, /* @ts-ignore */
|
|
92
|
+
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
93
|
+
/**
|
|
94
|
+
* Accent for the indicator. Accepts a semantic name (`primary`), a CSS colour
|
|
95
|
+
* literal (`#0d6efd`, `oklch(...)`) or a `var(...)` reference — normalised by
|
|
96
|
+
* `resolveHubAccent()` into the single `--hub-loading-accent` slot.
|
|
97
|
+
*/
|
|
98
|
+
color = input(this.config.color, /* @ts-ignore */
|
|
99
|
+
...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
|
|
100
|
+
/** Paints the translucent scrim. Ignored in `inline` mode, which covers nothing. */
|
|
101
|
+
backdrop = input(this.config.backdrop, { ...(ngDevMode ? { debugName: "backdrop" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
102
|
+
/** Accessible label announced by the host's `role="status"` live region. */
|
|
103
|
+
ariaLabel = input(this.config.ariaLabel, /* @ts-ignore */
|
|
104
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
105
|
+
/** Mode and size modifiers; kept as one binding so a size change cannot drop the mode. */
|
|
106
|
+
_modifierClasses = computed(() => `hub-loading--${this.mode()} hub-loading--${this.size()}`, /* @ts-ignore */
|
|
107
|
+
...(ngDevMode ? [{ debugName: "_modifierClasses" }] : /* istanbul ignore next */ []));
|
|
108
|
+
/**
|
|
109
|
+
* The scrim only exists where the indicator actually covers something, so an
|
|
110
|
+
* inline block never paints a background it would have no reason to own.
|
|
111
|
+
*/
|
|
112
|
+
_showsBackdrop = computed(() => this.backdrop() && this.mode() !== 'inline', /* @ts-ignore */
|
|
113
|
+
...(ngDevMode ? [{ debugName: "_showsBackdrop" }] : /* istanbul ignore next */ []));
|
|
114
|
+
/**
|
|
115
|
+
* Single accent slot consumed by the stylesheet. `null` leaves the binding off
|
|
116
|
+
* entirely, so the token's own cascade default stays in effect.
|
|
117
|
+
*/
|
|
118
|
+
_accent = computed(() => resolveHubAccent(this.color()), /* @ts-ignore */
|
|
119
|
+
...(ngDevMode ? [{ debugName: "_accent" }] : /* istanbul ignore next */ []));
|
|
120
|
+
/** Motion modifier for the branding image; `none` adds no class at all. */
|
|
121
|
+
_imageClasses = computed(() => this.imageAnimation() === 'none' ? '' : `hub-loading__image--${this.imageAnimation()}`, /* @ts-ignore */
|
|
122
|
+
...(ngDevMode ? [{ debugName: "_imageClasses" }] : /* istanbul ignore next */ []));
|
|
123
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubLoadingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
124
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: HubLoadingComponent, isStandalone: true, selector: "hub-loading", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, image: { classPropertyName: "image", publicName: "image", isSignal: true, isRequired: false, transformFunction: null }, imageAnimation: { classPropertyName: "imageAnimation", publicName: "imageAnimation", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, backdrop: { classPropertyName: "backdrop", publicName: "backdrop", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "status", "aria-live": "polite", "aria-busy": "true" }, properties: { "class": "_modifierClasses()", "class.hub-loading--backdrop": "_showsBackdrop()", "attr.aria-label": "ariaLabel()", "style.--hub-loading-accent": "_accent()" }, classAttribute: "hub-loading" }, ngImport: i0, template: "@if (image(); as source) {\n\t<img class=\"hub-loading__image\" [class]=\"_imageClasses()\" [src]=\"source\" alt=\"\" aria-hidden=\"true\" />\n} @else {\n\t@switch (variant()) {\n\t\t@case ('dots') {\n\t\t\t<span class=\"hub-loading__indicator hub-loading__indicator--dots\" aria-hidden=\"true\">\n\t\t\t\t<span class=\"hub-loading__dot\"></span>\n\t\t\t\t<span class=\"hub-loading__dot\"></span>\n\t\t\t\t<span class=\"hub-loading__dot\"></span>\n\t\t\t</span>\n\t\t}\n\t\t@case ('bars') {\n\t\t\t<span class=\"hub-loading__indicator hub-loading__indicator--bars\" aria-hidden=\"true\">\n\t\t\t\t<span class=\"hub-loading__bar\"></span>\n\t\t\t\t<span class=\"hub-loading__bar\"></span>\n\t\t\t\t<span class=\"hub-loading__bar\"></span>\n\t\t\t\t<span class=\"hub-loading__bar\"></span>\n\t\t\t</span>\n\t\t}\n\t\t@case ('pulse') {\n\t\t\t<span class=\"hub-loading__indicator hub-loading__indicator--pulse\" aria-hidden=\"true\"></span>\n\t\t}\n\t\t@case ('ring') {\n\t\t\t<span class=\"hub-loading__indicator hub-loading__indicator--ring\" aria-hidden=\"true\"></span>\n\t\t}\n\t\t@default {\n\t\t\t<span class=\"hub-loading__indicator hub-loading__indicator--spinner\" aria-hidden=\"true\"></span>\n\t\t}\n\t}\n}\n\n@if (message()) {\n\t<p class=\"hub-loading__message\">{{ message() }}</p>\n}\n\n<ng-content />\n", styles: [":where(.hub-loading){--hub-loading-accent: var(--hub-sys-color-primary, #0d6efd);--hub-loading-size: 2.5rem;--hub-loading-thickness: calc(var(--hub-ref-border-width, 1px) * 3);--hub-loading-speed: .9s;--hub-loading-gap: var(--hub-sys-gap-2, var(--hub-ref-space-2, .5rem));--hub-loading-text-color: var(--hub-sys-text-primary, var(--hub-ref-color-gray-900, #212529));--hub-loading-font-size: var(--hub-ref-font-size-sm, .875rem);--hub-loading-backdrop-bg: color-mix(in srgb, var(--hub-sys-surface-page, #ffffff) 72%, transparent);--hub-loading-backdrop-blur: 2px;--hub-loading-z-index: var(--hub-sys-zindex-modal, 1055);--hub-loading-image-size: var(--hub-loading-size)}.hub-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--hub-loading-gap);color:var(--hub-loading-text-color);font-size:var(--hub-loading-font-size)}.hub-loading--overlay{position:absolute;inset:0}.hub-loading--fullscreen{position:fixed;inset:0;z-index:var(--hub-loading-z-index)}.hub-loading--backdrop{background:var(--hub-loading-backdrop-bg);-webkit-backdrop-filter:blur(var(--hub-loading-backdrop-blur));backdrop-filter:blur(var(--hub-loading-backdrop-blur))}.hub-loading--sm{--hub-loading-size: 1.5rem;--hub-loading-thickness: calc(var(--hub-ref-border-width, 1px) * 2);--hub-loading-font-size: var(--hub-ref-font-size-xs, .75rem)}.hub-loading--lg{--hub-loading-size: 4rem;--hub-loading-thickness: calc(var(--hub-ref-border-width, 1px) * 4);--hub-loading-font-size: var(--hub-ref-font-size-base, 1rem)}.hub-loading__indicator{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;inline-size:var(--hub-loading-size);block-size:var(--hub-loading-size);color:var(--hub-loading-accent)}.hub-loading__indicator--spinner{border:var(--hub-loading-thickness) solid color-mix(in srgb,var(--hub-loading-accent) 20%,transparent);border-block-start-color:var(--hub-loading-accent);border-radius:50%;animation:hub-loading-spin var(--hub-loading-speed) linear infinite}.hub-loading__indicator--ring{background:conic-gradient(from 0deg,transparent 0%,var(--hub-loading-accent) 100%);border-radius:50%;-webkit-mask:radial-gradient(farthest-side,transparent calc(100% - var(--hub-loading-thickness)),#000 calc(100% - var(--hub-loading-thickness)));mask:radial-gradient(farthest-side,transparent calc(100% - var(--hub-loading-thickness)),#000 calc(100% - var(--hub-loading-thickness)));animation:hub-loading-spin var(--hub-loading-speed) linear infinite}.hub-loading__indicator--dots{inline-size:auto;gap:calc(var(--hub-loading-size) / 5)}.hub-loading__dot{flex:0 0 auto;inline-size:calc(var(--hub-loading-size) / 4);block-size:calc(var(--hub-loading-size) / 4);background:var(--hub-loading-accent);border-radius:50%;animation:hub-loading-dot var(--hub-loading-speed) ease-in-out infinite}.hub-loading__dot:nth-child(2){animation-delay:calc(var(--hub-loading-speed) / 6)}.hub-loading__dot:nth-child(3){animation-delay:calc(var(--hub-loading-speed) / 3)}.hub-loading__indicator--bars{gap:calc(var(--hub-loading-size) / 8)}.hub-loading__bar{flex:0 0 auto;inline-size:calc(var(--hub-loading-size) / 8);block-size:100%;background:var(--hub-loading-accent);border-radius:var(--hub-sys-radius-pill, 50rem);transform-origin:center;animation:hub-loading-bar var(--hub-loading-speed) ease-in-out infinite}.hub-loading__bar:nth-child(2){animation-delay:calc(var(--hub-loading-speed) / 8)}.hub-loading__bar:nth-child(3){animation-delay:calc(var(--hub-loading-speed) / 4)}.hub-loading__bar:nth-child(4){animation-delay:calc(var(--hub-loading-speed) * 3 / 8)}.hub-loading__indicator--pulse{background:var(--hub-loading-accent);border-radius:50%;animation:hub-loading-pulse calc(var(--hub-loading-speed) * 1.4) ease-in-out infinite}.hub-loading__image{inline-size:var(--hub-loading-image-size);block-size:var(--hub-loading-image-size);object-fit:contain}.hub-loading__image--spin{animation:hub-loading-spin calc(var(--hub-loading-speed) * 1.6) linear infinite}.hub-loading__image--pulse{animation:hub-loading-pulse calc(var(--hub-loading-speed) * 1.6) ease-in-out infinite}.hub-loading__message{margin:0;color:var(--hub-loading-text-color);font-size:var(--hub-loading-font-size);text-align:center}@media(prefers-reduced-motion:reduce){.hub-loading{--hub-loading-speed: 2.4s}.hub-loading__indicator--spinner,.hub-loading__indicator--ring,.hub-loading__indicator--pulse,.hub-loading__dot,.hub-loading__bar,.hub-loading__image--spin,.hub-loading__image--pulse{animation-name:hub-loading-fade;animation-duration:var(--hub-loading-speed);animation-timing-function:ease-in-out}}@keyframes hub-loading-spin{to{transform:rotate(360deg)}}@keyframes hub-loading-dot{0%,80%,to{opacity:.3;transform:scale(.7)}40%{opacity:1;transform:scale(1)}}@keyframes hub-loading-bar{0%,to{transform:scaleY(.35)}50%{transform:scaleY(1)}}@keyframes hub-loading-pulse{0%,to{opacity:.35;transform:scale(.75)}50%{opacity:1;transform:scale(1)}}@keyframes hub-loading-fade{0%,to{opacity:.35}50%{opacity:1}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
125
|
+
}
|
|
126
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubLoadingComponent, decorators: [{
|
|
127
|
+
type: Component,
|
|
128
|
+
args: [{ selector: 'hub-loading', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
|
|
129
|
+
class: 'hub-loading',
|
|
130
|
+
role: 'status',
|
|
131
|
+
'aria-live': 'polite',
|
|
132
|
+
'aria-busy': 'true',
|
|
133
|
+
'[class]': '_modifierClasses()',
|
|
134
|
+
'[class.hub-loading--backdrop]': '_showsBackdrop()',
|
|
135
|
+
'[attr.aria-label]': 'ariaLabel()',
|
|
136
|
+
'[style.--hub-loading-accent]': '_accent()'
|
|
137
|
+
}, template: "@if (image(); as source) {\n\t<img class=\"hub-loading__image\" [class]=\"_imageClasses()\" [src]=\"source\" alt=\"\" aria-hidden=\"true\" />\n} @else {\n\t@switch (variant()) {\n\t\t@case ('dots') {\n\t\t\t<span class=\"hub-loading__indicator hub-loading__indicator--dots\" aria-hidden=\"true\">\n\t\t\t\t<span class=\"hub-loading__dot\"></span>\n\t\t\t\t<span class=\"hub-loading__dot\"></span>\n\t\t\t\t<span class=\"hub-loading__dot\"></span>\n\t\t\t</span>\n\t\t}\n\t\t@case ('bars') {\n\t\t\t<span class=\"hub-loading__indicator hub-loading__indicator--bars\" aria-hidden=\"true\">\n\t\t\t\t<span class=\"hub-loading__bar\"></span>\n\t\t\t\t<span class=\"hub-loading__bar\"></span>\n\t\t\t\t<span class=\"hub-loading__bar\"></span>\n\t\t\t\t<span class=\"hub-loading__bar\"></span>\n\t\t\t</span>\n\t\t}\n\t\t@case ('pulse') {\n\t\t\t<span class=\"hub-loading__indicator hub-loading__indicator--pulse\" aria-hidden=\"true\"></span>\n\t\t}\n\t\t@case ('ring') {\n\t\t\t<span class=\"hub-loading__indicator hub-loading__indicator--ring\" aria-hidden=\"true\"></span>\n\t\t}\n\t\t@default {\n\t\t\t<span class=\"hub-loading__indicator hub-loading__indicator--spinner\" aria-hidden=\"true\"></span>\n\t\t}\n\t}\n}\n\n@if (message()) {\n\t<p class=\"hub-loading__message\">{{ message() }}</p>\n}\n\n<ng-content />\n", styles: [":where(.hub-loading){--hub-loading-accent: var(--hub-sys-color-primary, #0d6efd);--hub-loading-size: 2.5rem;--hub-loading-thickness: calc(var(--hub-ref-border-width, 1px) * 3);--hub-loading-speed: .9s;--hub-loading-gap: var(--hub-sys-gap-2, var(--hub-ref-space-2, .5rem));--hub-loading-text-color: var(--hub-sys-text-primary, var(--hub-ref-color-gray-900, #212529));--hub-loading-font-size: var(--hub-ref-font-size-sm, .875rem);--hub-loading-backdrop-bg: color-mix(in srgb, var(--hub-sys-surface-page, #ffffff) 72%, transparent);--hub-loading-backdrop-blur: 2px;--hub-loading-z-index: var(--hub-sys-zindex-modal, 1055);--hub-loading-image-size: var(--hub-loading-size)}.hub-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--hub-loading-gap);color:var(--hub-loading-text-color);font-size:var(--hub-loading-font-size)}.hub-loading--overlay{position:absolute;inset:0}.hub-loading--fullscreen{position:fixed;inset:0;z-index:var(--hub-loading-z-index)}.hub-loading--backdrop{background:var(--hub-loading-backdrop-bg);-webkit-backdrop-filter:blur(var(--hub-loading-backdrop-blur));backdrop-filter:blur(var(--hub-loading-backdrop-blur))}.hub-loading--sm{--hub-loading-size: 1.5rem;--hub-loading-thickness: calc(var(--hub-ref-border-width, 1px) * 2);--hub-loading-font-size: var(--hub-ref-font-size-xs, .75rem)}.hub-loading--lg{--hub-loading-size: 4rem;--hub-loading-thickness: calc(var(--hub-ref-border-width, 1px) * 4);--hub-loading-font-size: var(--hub-ref-font-size-base, 1rem)}.hub-loading__indicator{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;inline-size:var(--hub-loading-size);block-size:var(--hub-loading-size);color:var(--hub-loading-accent)}.hub-loading__indicator--spinner{border:var(--hub-loading-thickness) solid color-mix(in srgb,var(--hub-loading-accent) 20%,transparent);border-block-start-color:var(--hub-loading-accent);border-radius:50%;animation:hub-loading-spin var(--hub-loading-speed) linear infinite}.hub-loading__indicator--ring{background:conic-gradient(from 0deg,transparent 0%,var(--hub-loading-accent) 100%);border-radius:50%;-webkit-mask:radial-gradient(farthest-side,transparent calc(100% - var(--hub-loading-thickness)),#000 calc(100% - var(--hub-loading-thickness)));mask:radial-gradient(farthest-side,transparent calc(100% - var(--hub-loading-thickness)),#000 calc(100% - var(--hub-loading-thickness)));animation:hub-loading-spin var(--hub-loading-speed) linear infinite}.hub-loading__indicator--dots{inline-size:auto;gap:calc(var(--hub-loading-size) / 5)}.hub-loading__dot{flex:0 0 auto;inline-size:calc(var(--hub-loading-size) / 4);block-size:calc(var(--hub-loading-size) / 4);background:var(--hub-loading-accent);border-radius:50%;animation:hub-loading-dot var(--hub-loading-speed) ease-in-out infinite}.hub-loading__dot:nth-child(2){animation-delay:calc(var(--hub-loading-speed) / 6)}.hub-loading__dot:nth-child(3){animation-delay:calc(var(--hub-loading-speed) / 3)}.hub-loading__indicator--bars{gap:calc(var(--hub-loading-size) / 8)}.hub-loading__bar{flex:0 0 auto;inline-size:calc(var(--hub-loading-size) / 8);block-size:100%;background:var(--hub-loading-accent);border-radius:var(--hub-sys-radius-pill, 50rem);transform-origin:center;animation:hub-loading-bar var(--hub-loading-speed) ease-in-out infinite}.hub-loading__bar:nth-child(2){animation-delay:calc(var(--hub-loading-speed) / 8)}.hub-loading__bar:nth-child(3){animation-delay:calc(var(--hub-loading-speed) / 4)}.hub-loading__bar:nth-child(4){animation-delay:calc(var(--hub-loading-speed) * 3 / 8)}.hub-loading__indicator--pulse{background:var(--hub-loading-accent);border-radius:50%;animation:hub-loading-pulse calc(var(--hub-loading-speed) * 1.4) ease-in-out infinite}.hub-loading__image{inline-size:var(--hub-loading-image-size);block-size:var(--hub-loading-image-size);object-fit:contain}.hub-loading__image--spin{animation:hub-loading-spin calc(var(--hub-loading-speed) * 1.6) linear infinite}.hub-loading__image--pulse{animation:hub-loading-pulse calc(var(--hub-loading-speed) * 1.6) ease-in-out infinite}.hub-loading__message{margin:0;color:var(--hub-loading-text-color);font-size:var(--hub-loading-font-size);text-align:center}@media(prefers-reduced-motion:reduce){.hub-loading{--hub-loading-speed: 2.4s}.hub-loading__indicator--spinner,.hub-loading__indicator--ring,.hub-loading__indicator--pulse,.hub-loading__dot,.hub-loading__bar,.hub-loading__image--spin,.hub-loading__image--pulse{animation-name:hub-loading-fade;animation-duration:var(--hub-loading-speed);animation-timing-function:ease-in-out}}@keyframes hub-loading-spin{to{transform:rotate(360deg)}}@keyframes hub-loading-dot{0%,80%,to{opacity:.3;transform:scale(.7)}40%{opacity:1;transform:scale(1)}}@keyframes hub-loading-bar{0%,to{transform:scaleY(.35)}50%{transform:scaleY(1)}}@keyframes hub-loading-pulse{0%,to{opacity:.35;transform:scale(.75)}50%{opacity:1;transform:scale(1)}}@keyframes hub-loading-fade{0%,to{opacity:.35}50%{opacity:1}}\n"] }]
|
|
138
|
+
}], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], image: [{ type: i0.Input, args: [{ isSignal: true, alias: "image", required: false }] }], imageAnimation: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageAnimation", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], backdrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "backdrop", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Drives a single application-wide fullscreen loading overlay.
|
|
142
|
+
*
|
|
143
|
+
* Concurrency is handled with a reference counter rather than a boolean, because
|
|
144
|
+
* independent callers overlap constantly (two parallel requests, a resolver plus a
|
|
145
|
+
* component): the overlay appears on the first `show()` and only disappears once
|
|
146
|
+
* every caller has balanced it with a `hide()`. A caller that forgets to hide would
|
|
147
|
+
* strand the overlay, so {@link hideAll} exists as the explicit escape hatch — use it
|
|
148
|
+
* from an error handler or a route change, never as a substitute for balanced calls.
|
|
149
|
+
*
|
|
150
|
+
* Server-side there is no DOM to mount into, so only the counter runs: `isLoading`
|
|
151
|
+
* stays truthful and hydration finds no orphan overlay markup.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```typescript
|
|
155
|
+
* private readonly loading = inject(HubLoadingService);
|
|
156
|
+
*
|
|
157
|
+
* async save(): Promise<void> {
|
|
158
|
+
* this.loading.show({ message: 'Saving…' });
|
|
159
|
+
* try {
|
|
160
|
+
* await this.api.save();
|
|
161
|
+
* } finally {
|
|
162
|
+
* this.loading.hide();
|
|
163
|
+
* }
|
|
164
|
+
* }
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
class HubLoadingService {
|
|
168
|
+
appRef = inject(ApplicationRef);
|
|
169
|
+
document = inject(DOCUMENT);
|
|
170
|
+
platformId = inject(PLATFORM_ID);
|
|
171
|
+
config = inject(HUB_LOADING_CONFIG);
|
|
172
|
+
/** Number of callers currently requesting the overlay. */
|
|
173
|
+
pending = signal(0, /* @ts-ignore */
|
|
174
|
+
...(ngDevMode ? [{ debugName: "pending" }] : /* istanbul ignore next */ []));
|
|
175
|
+
/** Live reference to the mounted overlay; `null` whenever nothing is showing. */
|
|
176
|
+
overlayRef = null;
|
|
177
|
+
/**
|
|
178
|
+
* Options accumulated by the active `show()` / `update()` calls, layered over
|
|
179
|
+
* `HUB_LOADING_CONFIG`. Reset once the counter reaches zero so a later overlay
|
|
180
|
+
* never inherits a stale message from a finished operation.
|
|
181
|
+
*/
|
|
182
|
+
options = {};
|
|
183
|
+
/** True while at least one caller is still waiting. Safe to read during SSR. */
|
|
184
|
+
isLoading = computed(() => this.pending() > 0, /* @ts-ignore */
|
|
185
|
+
...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
|
|
186
|
+
/**
|
|
187
|
+
* Registers one caller and mounts the overlay if it is not up yet.
|
|
188
|
+
*
|
|
189
|
+
* @param options - Presentation overrides merged over the application defaults;
|
|
190
|
+
* only the keys supplied are changed, so nested calls compose instead of resetting.
|
|
191
|
+
*/
|
|
192
|
+
show(options = {}) {
|
|
193
|
+
this.mergeOptions(options);
|
|
194
|
+
this.pending.update((count) => count + 1);
|
|
195
|
+
this.mount();
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Retires one caller, tearing the overlay down once none are left.
|
|
199
|
+
* Extra calls are harmless: the counter is clamped at zero rather than going
|
|
200
|
+
* negative, so a stray `hide()` cannot make a later `show()` a no-op.
|
|
201
|
+
*/
|
|
202
|
+
hide() {
|
|
203
|
+
this.pending.update((count) => Math.max(0, count - 1));
|
|
204
|
+
if (this.pending() === 0) {
|
|
205
|
+
this.unmount();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/** Drops every pending caller and removes the overlay immediately. */
|
|
209
|
+
hideAll() {
|
|
210
|
+
this.pending.set(0);
|
|
211
|
+
this.unmount();
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Re-dresses the overlay while it stays up — a progress message that changes
|
|
215
|
+
* mid-operation, a variant swap — without touching the reference counter.
|
|
216
|
+
*
|
|
217
|
+
* @param options - Presentation overrides merged over the active ones.
|
|
218
|
+
*/
|
|
219
|
+
update(options) {
|
|
220
|
+
this.mergeOptions(options);
|
|
221
|
+
this.applyOptions();
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Copies only the keys the caller actually supplied.
|
|
225
|
+
*
|
|
226
|
+
* A plain spread would let an `undefined` property erase a configured default,
|
|
227
|
+
* which would make `{ message: undefined }` and `{ message: null }` behave the
|
|
228
|
+
* same; here `undefined` means "leave it alone" and `null` means "clear it".
|
|
229
|
+
*/
|
|
230
|
+
mergeOptions(options) {
|
|
231
|
+
for (const [key, value] of Object.entries(options)) {
|
|
232
|
+
if (value !== undefined) {
|
|
233
|
+
this.options[key] = value;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Creates the overlay on `document.body` once, outside any component subtree, so
|
|
239
|
+
* it is never clipped by an ancestor's `overflow` or stacking context.
|
|
240
|
+
*/
|
|
241
|
+
mount() {
|
|
242
|
+
if (!isPlatformBrowser(this.platformId)) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (this.overlayRef) {
|
|
246
|
+
this.applyOptions();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
// A `show()` racing application teardown (a destroyed TestBed, an HMR reload)
|
|
250
|
+
// would otherwise touch a dead environment injector and throw NG0205.
|
|
251
|
+
if (this.appRef.destroyed) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const ref = createComponent(HubLoadingComponent, { environmentInjector: this.appRef.injector });
|
|
255
|
+
this.overlayRef = ref;
|
|
256
|
+
this.appRef.attachView(ref.hostView);
|
|
257
|
+
this.document.body.appendChild(ref.location.nativeElement);
|
|
258
|
+
this.applyOptions();
|
|
259
|
+
}
|
|
260
|
+
/** Destroys the overlay and forgets the accumulated options. */
|
|
261
|
+
unmount() {
|
|
262
|
+
const ref = this.overlayRef;
|
|
263
|
+
this.overlayRef = null;
|
|
264
|
+
this.options = {};
|
|
265
|
+
if (!ref) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
this.appRef.detachView(ref.hostView);
|
|
269
|
+
ref.destroy();
|
|
270
|
+
// `destroy()` tears down the view but leaves the host node where we put it.
|
|
271
|
+
ref.location.nativeElement.remove();
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Pushes the resolved options onto the overlay.
|
|
275
|
+
*
|
|
276
|
+
* Change detection is run by hand: a view attached through `attachView()` sits
|
|
277
|
+
* outside the signal graph's "mark ancestors dirty" traversal, so it would not
|
|
278
|
+
* repaint on its own when an input changes.
|
|
279
|
+
*/
|
|
280
|
+
applyOptions() {
|
|
281
|
+
const ref = this.overlayRef;
|
|
282
|
+
if (!ref) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const resolved = { ...this.config, ...this.options };
|
|
286
|
+
ref.setInput('mode', 'fullscreen');
|
|
287
|
+
ref.setInput('variant', resolved.variant);
|
|
288
|
+
ref.setInput('image', resolved.image);
|
|
289
|
+
ref.setInput('imageAnimation', resolved.imageAnimation);
|
|
290
|
+
ref.setInput('message', resolved.message);
|
|
291
|
+
ref.setInput('size', resolved.size);
|
|
292
|
+
ref.setInput('color', resolved.color);
|
|
293
|
+
ref.setInput('backdrop', resolved.backdrop);
|
|
294
|
+
ref.setInput('ariaLabel', resolved.ariaLabel);
|
|
295
|
+
ref.changeDetectorRef.detectChanges();
|
|
296
|
+
}
|
|
297
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubLoadingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
298
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubLoadingService, providedIn: 'root' });
|
|
299
|
+
}
|
|
300
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubLoadingService, decorators: [{
|
|
301
|
+
type: Injectable,
|
|
302
|
+
args: [{ providedIn: 'root' }]
|
|
303
|
+
}] });
|
|
304
|
+
|
|
305
|
+
/** Public API surface of ng-hub-ui-loading. */
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Generated bundle index. Do not edit.
|
|
309
|
+
*/
|
|
310
|
+
|
|
311
|
+
export { HUB_LOADING_CONFIG, HUB_LOADING_DEFAULT_CONFIG, HubLoadingComponent, HubLoadingService, provideHubLoading };
|
|
312
|
+
//# sourceMappingURL=ng-hub-ui-loading.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ng-hub-ui-loading.mjs","sources":["../../../projects/loading/src/lib/loading-config.ts","../../../projects/loading/src/lib/components/loading/loading.component.ts","../../../projects/loading/src/lib/components/loading/loading.component.html","../../../projects/loading/src/lib/services/loading.service.ts","../../../projects/loading/src/public-api.ts","../../../projects/loading/src/ng-hub-ui-loading.ts"],"sourcesContent":["import { EnvironmentProviders, InjectionToken, makeEnvironmentProviders } from '@angular/core';\nimport { HubLoadingConfig } from './models/loading.types';\n\n/**\n * Neutral defaults applied when an application provides no configuration.\n *\n * These are the values documented as each input's default, so overriding the\n * token silently re-bases the whole application without touching a template.\n */\nexport const HUB_LOADING_DEFAULT_CONFIG: HubLoadingConfig = {\n\tmessage: null,\n\tvariant: 'spinner',\n\timage: null,\n\timageAnimation: 'none',\n\tsize: 'md',\n\tcolor: null,\n\tbackdrop: true,\n\tariaLabel: 'Loading'\n};\n\n/**\n * Resolved defaults shared by `<hub-loading>` and `HubLoadingService`.\n *\n * Declared with a root factory so the token is always injectable, even when the\n * application never calls {@link provideHubLoading}.\n */\nexport const HUB_LOADING_CONFIG = new InjectionToken<HubLoadingConfig>('HUB_LOADING_CONFIG', {\n\tprovidedIn: 'root',\n\tfactory: () => HUB_LOADING_DEFAULT_CONFIG\n});\n\n/**\n * Registers application-wide loading defaults — typically the brand image, the\n * preferred variant and a translated label — so individual call sites stay bare.\n *\n * @param config - Values overriding {@link HUB_LOADING_DEFAULT_CONFIG}; omitted keys keep their default.\n * @returns Environment providers for the application bootstrap.\n */\nexport function provideHubLoading(config: Partial<HubLoadingConfig> = {}): EnvironmentProviders {\n\treturn makeEnvironmentProviders([\n\t\t{\n\t\t\tprovide: HUB_LOADING_CONFIG,\n\t\t\tuseValue: { ...HUB_LOADING_DEFAULT_CONFIG, ...config }\n\t\t}\n\t]);\n}\n","import {\n\tbooleanAttribute,\n\tChangeDetectionStrategy,\n\tComponent,\n\tcomputed,\n\tinject,\n\tinput,\n\tViewEncapsulation\n} from '@angular/core';\nimport { resolveHubAccent } from 'ng-hub-ui-utils';\nimport { HUB_LOADING_CONFIG } from '../../loading-config';\nimport { HubLoadingImageAnimation, HubLoadingMode, HubLoadingSize, HubLoadingVariant } from '../../models/loading.types';\n\n/**\n * Activity indicator rendered inline, over its container or over the viewport.\n *\n * Every input defaults to the injected `HUB_LOADING_CONFIG`, so `provideHubLoading()`\n * re-bases an entire application (brand image, variant, translated label) without\n * touching a single template, while a per-instance binding still wins locally.\n *\n * Styles are unencapsulated on purpose: the host carries the `hub-loading` class and\n * the token block, so consumers can retheme the indicator from a global stylesheet —\n * and so the service-mounted overlay, created outside any component's style scope,\n * is still painted.\n *\n * @example\n * ```html\n * <hub-loading variant=\"dots\" message=\"Loading orders…\" />\n *\n * <div style=\"position: relative\">\n * <hub-loading mode=\"overlay\" color=\"primary\" />\n * </div>\n * ```\n */\n@Component({\n\tselector: 'hub-loading',\n\tstandalone: true,\n\tchangeDetection: ChangeDetectionStrategy.OnPush,\n\tencapsulation: ViewEncapsulation.None,\n\ttemplateUrl: './loading.component.html',\n\tstyleUrl: './loading.component.scss',\n\thost: {\n\t\tclass: 'hub-loading',\n\t\trole: 'status',\n\t\t'aria-live': 'polite',\n\t\t'aria-busy': 'true',\n\t\t'[class]': '_modifierClasses()',\n\t\t'[class.hub-loading--backdrop]': '_showsBackdrop()',\n\t\t'[attr.aria-label]': 'ariaLabel()',\n\t\t'[style.--hub-loading-accent]': '_accent()'\n\t}\n})\nexport class HubLoadingComponent {\n\t/** Application-wide defaults; also the source of every input's default value. */\n\tprivate readonly config = inject(HUB_LOADING_CONFIG);\n\n\t/**\n\t * Placement of the indicator. `overlay` needs a positioned ancestor to cover;\n\t * `fullscreen` is fixed to the viewport and layered at `--hub-loading-z-index`.\n\t */\n\treadonly mode = input<HubLoadingMode>('inline');\n\n\t/** Built-in CSS indicator rendered when no {@link image} is supplied. */\n\treadonly variant = input<HubLoadingVariant>(this.config.variant);\n\n\t/** URL or data URI shown instead of the built-in indicator. */\n\treadonly image = input<string | null>(this.config.image);\n\n\t/** Motion applied to {@link image}; inert while no image is set. */\n\treadonly imageAnimation = input<HubLoadingImageAnimation>(this.config.imageAnimation);\n\n\t/** Text rendered below the indicator. */\n\treadonly message = input<string | null>(this.config.message);\n\n\t/** Size step feeding `--hub-loading-size`; the token remains overridable on its own. */\n\treadonly size = input<HubLoadingSize>(this.config.size);\n\n\t/**\n\t * Accent for the indicator. Accepts a semantic name (`primary`), a CSS colour\n\t * literal (`#0d6efd`, `oklch(...)`) or a `var(...)` reference — normalised by\n\t * `resolveHubAccent()` into the single `--hub-loading-accent` slot.\n\t */\n\treadonly color = input<string | null>(this.config.color);\n\n\t/** Paints the translucent scrim. Ignored in `inline` mode, which covers nothing. */\n\treadonly backdrop = input(this.config.backdrop, { transform: booleanAttribute });\n\n\t/** Accessible label announced by the host's `role=\"status\"` live region. */\n\treadonly ariaLabel = input<string>(this.config.ariaLabel);\n\n\t/** Mode and size modifiers; kept as one binding so a size change cannot drop the mode. */\n\tprotected readonly _modifierClasses = computed(() => `hub-loading--${this.mode()} hub-loading--${this.size()}`);\n\n\t/**\n\t * The scrim only exists where the indicator actually covers something, so an\n\t * inline block never paints a background it would have no reason to own.\n\t */\n\tprotected readonly _showsBackdrop = computed(() => this.backdrop() && this.mode() !== 'inline');\n\n\t/**\n\t * Single accent slot consumed by the stylesheet. `null` leaves the binding off\n\t * entirely, so the token's own cascade default stays in effect.\n\t */\n\tprotected readonly _accent = computed(() => resolveHubAccent(this.color()));\n\n\t/** Motion modifier for the branding image; `none` adds no class at all. */\n\tprotected readonly _imageClasses = computed(() =>\n\t\tthis.imageAnimation() === 'none' ? '' : `hub-loading__image--${this.imageAnimation()}`\n\t);\n}\n","@if (image(); as source) {\n\t<img class=\"hub-loading__image\" [class]=\"_imageClasses()\" [src]=\"source\" alt=\"\" aria-hidden=\"true\" />\n} @else {\n\t@switch (variant()) {\n\t\t@case ('dots') {\n\t\t\t<span class=\"hub-loading__indicator hub-loading__indicator--dots\" aria-hidden=\"true\">\n\t\t\t\t<span class=\"hub-loading__dot\"></span>\n\t\t\t\t<span class=\"hub-loading__dot\"></span>\n\t\t\t\t<span class=\"hub-loading__dot\"></span>\n\t\t\t</span>\n\t\t}\n\t\t@case ('bars') {\n\t\t\t<span class=\"hub-loading__indicator hub-loading__indicator--bars\" aria-hidden=\"true\">\n\t\t\t\t<span class=\"hub-loading__bar\"></span>\n\t\t\t\t<span class=\"hub-loading__bar\"></span>\n\t\t\t\t<span class=\"hub-loading__bar\"></span>\n\t\t\t\t<span class=\"hub-loading__bar\"></span>\n\t\t\t</span>\n\t\t}\n\t\t@case ('pulse') {\n\t\t\t<span class=\"hub-loading__indicator hub-loading__indicator--pulse\" aria-hidden=\"true\"></span>\n\t\t}\n\t\t@case ('ring') {\n\t\t\t<span class=\"hub-loading__indicator hub-loading__indicator--ring\" aria-hidden=\"true\"></span>\n\t\t}\n\t\t@default {\n\t\t\t<span class=\"hub-loading__indicator hub-loading__indicator--spinner\" aria-hidden=\"true\"></span>\n\t\t}\n\t}\n}\n\n@if (message()) {\n\t<p class=\"hub-loading__message\">{{ message() }}</p>\n}\n\n<ng-content />\n","import { DOCUMENT, isPlatformBrowser } from '@angular/common';\nimport {\n\tApplicationRef,\n\tComponentRef,\n\tInjectable,\n\tPLATFORM_ID,\n\tSignal,\n\tcomputed,\n\tcreateComponent,\n\tinject,\n\tsignal\n} from '@angular/core';\nimport { HubLoadingComponent } from '../components/loading/loading.component';\nimport { HUB_LOADING_CONFIG } from '../loading-config';\nimport { HubLoadingOptions } from '../models/loading.types';\n\n/**\n * Drives a single application-wide fullscreen loading overlay.\n *\n * Concurrency is handled with a reference counter rather than a boolean, because\n * independent callers overlap constantly (two parallel requests, a resolver plus a\n * component): the overlay appears on the first `show()` and only disappears once\n * every caller has balanced it with a `hide()`. A caller that forgets to hide would\n * strand the overlay, so {@link hideAll} exists as the explicit escape hatch — use it\n * from an error handler or a route change, never as a substitute for balanced calls.\n *\n * Server-side there is no DOM to mount into, so only the counter runs: `isLoading`\n * stays truthful and hydration finds no orphan overlay markup.\n *\n * @example\n * ```typescript\n * private readonly loading = inject(HubLoadingService);\n *\n * async save(): Promise<void> {\n * this.loading.show({ message: 'Saving…' });\n * try {\n * await this.api.save();\n * } finally {\n * this.loading.hide();\n * }\n * }\n * ```\n */\n@Injectable({ providedIn: 'root' })\nexport class HubLoadingService {\n\tprivate readonly appRef = inject(ApplicationRef);\n\tprivate readonly document = inject(DOCUMENT);\n\tprivate readonly platformId = inject(PLATFORM_ID);\n\tprivate readonly config = inject(HUB_LOADING_CONFIG);\n\n\t/** Number of callers currently requesting the overlay. */\n\tprivate readonly pending = signal(0);\n\n\t/** Live reference to the mounted overlay; `null` whenever nothing is showing. */\n\tprivate overlayRef: ComponentRef<HubLoadingComponent> | null = null;\n\n\t/**\n\t * Options accumulated by the active `show()` / `update()` calls, layered over\n\t * `HUB_LOADING_CONFIG`. Reset once the counter reaches zero so a later overlay\n\t * never inherits a stale message from a finished operation.\n\t */\n\tprivate options: HubLoadingOptions = {};\n\n\t/** True while at least one caller is still waiting. Safe to read during SSR. */\n\treadonly isLoading: Signal<boolean> = computed(() => this.pending() > 0);\n\n\t/**\n\t * Registers one caller and mounts the overlay if it is not up yet.\n\t *\n\t * @param options - Presentation overrides merged over the application defaults;\n\t * only the keys supplied are changed, so nested calls compose instead of resetting.\n\t */\n\tshow(options: HubLoadingOptions = {}): void {\n\t\tthis.mergeOptions(options);\n\t\tthis.pending.update((count) => count + 1);\n\t\tthis.mount();\n\t}\n\n\t/**\n\t * Retires one caller, tearing the overlay down once none are left.\n\t * Extra calls are harmless: the counter is clamped at zero rather than going\n\t * negative, so a stray `hide()` cannot make a later `show()` a no-op.\n\t */\n\thide(): void {\n\t\tthis.pending.update((count) => Math.max(0, count - 1));\n\n\t\tif (this.pending() === 0) {\n\t\t\tthis.unmount();\n\t\t}\n\t}\n\n\t/** Drops every pending caller and removes the overlay immediately. */\n\thideAll(): void {\n\t\tthis.pending.set(0);\n\t\tthis.unmount();\n\t}\n\n\t/**\n\t * Re-dresses the overlay while it stays up — a progress message that changes\n\t * mid-operation, a variant swap — without touching the reference counter.\n\t *\n\t * @param options - Presentation overrides merged over the active ones.\n\t */\n\tupdate(options: HubLoadingOptions): void {\n\t\tthis.mergeOptions(options);\n\t\tthis.applyOptions();\n\t}\n\n\t/**\n\t * Copies only the keys the caller actually supplied.\n\t *\n\t * A plain spread would let an `undefined` property erase a configured default,\n\t * which would make `{ message: undefined }` and `{ message: null }` behave the\n\t * same; here `undefined` means \"leave it alone\" and `null` means \"clear it\".\n\t */\n\tprivate mergeOptions(options: HubLoadingOptions): void {\n\t\tfor (const [key, value] of Object.entries(options)) {\n\t\t\tif (value !== undefined) {\n\t\t\t\t(this.options as Record<string, unknown>)[key] = value;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Creates the overlay on `document.body` once, outside any component subtree, so\n\t * it is never clipped by an ancestor's `overflow` or stacking context.\n\t */\n\tprivate mount(): void {\n\t\tif (!isPlatformBrowser(this.platformId)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.overlayRef) {\n\t\t\tthis.applyOptions();\n\t\t\treturn;\n\t\t}\n\n\t\t// A `show()` racing application teardown (a destroyed TestBed, an HMR reload)\n\t\t// would otherwise touch a dead environment injector and throw NG0205.\n\t\tif (this.appRef.destroyed) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst ref = createComponent(HubLoadingComponent, { environmentInjector: this.appRef.injector });\n\t\tthis.overlayRef = ref;\n\t\tthis.appRef.attachView(ref.hostView);\n\t\tthis.document.body.appendChild(ref.location.nativeElement);\n\t\tthis.applyOptions();\n\t}\n\n\t/** Destroys the overlay and forgets the accumulated options. */\n\tprivate unmount(): void {\n\t\tconst ref = this.overlayRef;\n\t\tthis.overlayRef = null;\n\t\tthis.options = {};\n\n\t\tif (!ref) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.appRef.detachView(ref.hostView);\n\t\tref.destroy();\n\t\t// `destroy()` tears down the view but leaves the host node where we put it.\n\t\tref.location.nativeElement.remove();\n\t}\n\n\t/**\n\t * Pushes the resolved options onto the overlay.\n\t *\n\t * Change detection is run by hand: a view attached through `attachView()` sits\n\t * outside the signal graph's \"mark ancestors dirty\" traversal, so it would not\n\t * repaint on its own when an input changes.\n\t */\n\tprivate applyOptions(): void {\n\t\tconst ref = this.overlayRef;\n\n\t\tif (!ref) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst resolved = { ...this.config, ...this.options };\n\t\tref.setInput('mode', 'fullscreen');\n\t\tref.setInput('variant', resolved.variant);\n\t\tref.setInput('image', resolved.image);\n\t\tref.setInput('imageAnimation', resolved.imageAnimation);\n\t\tref.setInput('message', resolved.message);\n\t\tref.setInput('size', resolved.size);\n\t\tref.setInput('color', resolved.color);\n\t\tref.setInput('backdrop', resolved.backdrop);\n\t\tref.setInput('ariaLabel', resolved.ariaLabel);\n\t\tref.changeDetectorRef.detectChanges();\n\t}\n}\n","/** Public API surface of ng-hub-ui-loading. */\nexport { HubLoadingComponent } from './lib/components/loading/loading.component';\nexport { HubLoadingService } from './lib/services/loading.service';\nexport { HUB_LOADING_CONFIG, HUB_LOADING_DEFAULT_CONFIG, provideHubLoading } from './lib/loading-config';\nexport type {\n\tHubLoadingConfig,\n\tHubLoadingImageAnimation,\n\tHubLoadingMode,\n\tHubLoadingOptions,\n\tHubLoadingSize,\n\tHubLoadingVariant\n} from './lib/models/loading.types';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ng-hub-ui-loading",
|
|
3
|
+
"version": "22.0.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Customizable loading indicator for Angular: inline block, container overlay or fullscreen, with CSS-only variants, branding image and a programmatic service. Part of the ng-hub-ui family.",
|
|
6
|
+
"author": "Carlos Morcillo <carlos.morcillo@me.com> (https://www.carlosmorcillo.com)",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/carlos-morcillo/ng-hub-ui-loading.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/carlos-morcillo/ng-hub-ui-loading/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://hubui.dev/en/loading/overview/",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"angular",
|
|
17
|
+
"loading",
|
|
18
|
+
"spinner",
|
|
19
|
+
"loader",
|
|
20
|
+
"overlay",
|
|
21
|
+
"ng-hub-ui",
|
|
22
|
+
"standalone"
|
|
23
|
+
],
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@angular/common": ">=21.0.0",
|
|
26
|
+
"@angular/core": ">=21.0.0",
|
|
27
|
+
"ng-hub-ui-utils": ">=22.8.0"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"tslib": "^2.3.0"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"sideEffects": false,
|
|
36
|
+
"module": "fesm2022/ng-hub-ui-loading.mjs",
|
|
37
|
+
"typings": "types/ng-hub-ui-loading.d.ts",
|
|
38
|
+
"exports": {
|
|
39
|
+
"./package.json": {
|
|
40
|
+
"default": "./package.json"
|
|
41
|
+
},
|
|
42
|
+
".": {
|
|
43
|
+
"types": "./types/ng-hub-ui-loading.d.ts",
|
|
44
|
+
"default": "./fesm2022/ng-hub-ui-loading.mjs"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"type": "module"
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@forward 'mixins/loading-theme';
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// hub-loading-theme — one-call theming for `<hub-loading>`
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// Override the `--hub-loading-*` design tokens in a single include. Every
|
|
6
|
+
// parameter is OPTIONAL and defaults to `null`: only the parameters you pass
|
|
7
|
+
// are emitted, so a theme that cares about the accent alone does not freeze
|
|
8
|
+
// the other ten tokens at today's defaults. Token-based and self-contained
|
|
9
|
+
// (no Bootstrap dependencies).
|
|
10
|
+
//
|
|
11
|
+
// One accent drives all five indicator variants, so `$accent` recolours the
|
|
12
|
+
// spinner, the dots, the bars, the pulse and the ring together. `$backdrop-bg`
|
|
13
|
+
// and `$backdrop-blur` only reach the `overlay` and `fullscreen` modes — an
|
|
14
|
+
// inline block covers nothing and paints no scrim.
|
|
15
|
+
//
|
|
16
|
+
// @example
|
|
17
|
+
//
|
|
18
|
+
// @use 'ng-hub-ui-loading/styles' as *;
|
|
19
|
+
//
|
|
20
|
+
// .app-shell--dark {
|
|
21
|
+
// @include hub-loading-theme(
|
|
22
|
+
// $accent: #7dd3fc,
|
|
23
|
+
// $text-color: rgba(255, 255, 255, 0.82),
|
|
24
|
+
// $backdrop-bg: rgba(15, 23, 42, 0.72),
|
|
25
|
+
// $speed: 1.2s
|
|
26
|
+
// );
|
|
27
|
+
// }
|
|
28
|
+
//
|
|
29
|
+
// scss-docs-start hub-loading-theme
|
|
30
|
+
@mixin hub-loading-theme(
|
|
31
|
+
$accent: null,
|
|
32
|
+
$size: null,
|
|
33
|
+
$thickness: null,
|
|
34
|
+
$speed: null,
|
|
35
|
+
$gap: null,
|
|
36
|
+
$text-color: null,
|
|
37
|
+
$font-size: null,
|
|
38
|
+
$backdrop-bg: null,
|
|
39
|
+
$backdrop-blur: null,
|
|
40
|
+
$z-index: null,
|
|
41
|
+
$image-size: null
|
|
42
|
+
) {
|
|
43
|
+
:where(.hub-loading) {
|
|
44
|
+
@if $accent != null {
|
|
45
|
+
--hub-loading-accent: #{$accent};
|
|
46
|
+
}
|
|
47
|
+
@if $size != null {
|
|
48
|
+
--hub-loading-size: #{$size};
|
|
49
|
+
}
|
|
50
|
+
@if $thickness != null {
|
|
51
|
+
--hub-loading-thickness: #{$thickness};
|
|
52
|
+
}
|
|
53
|
+
@if $speed != null {
|
|
54
|
+
--hub-loading-speed: #{$speed};
|
|
55
|
+
}
|
|
56
|
+
@if $gap != null {
|
|
57
|
+
--hub-loading-gap: #{$gap};
|
|
58
|
+
}
|
|
59
|
+
@if $text-color != null {
|
|
60
|
+
--hub-loading-text-color: #{$text-color};
|
|
61
|
+
}
|
|
62
|
+
@if $font-size != null {
|
|
63
|
+
--hub-loading-font-size: #{$font-size};
|
|
64
|
+
}
|
|
65
|
+
@if $backdrop-bg != null {
|
|
66
|
+
--hub-loading-backdrop-bg: #{$backdrop-bg};
|
|
67
|
+
}
|
|
68
|
+
@if $backdrop-blur != null {
|
|
69
|
+
--hub-loading-backdrop-blur: #{$backdrop-blur};
|
|
70
|
+
}
|
|
71
|
+
@if $z-index != null {
|
|
72
|
+
--hub-loading-z-index: #{$z-index};
|
|
73
|
+
}
|
|
74
|
+
@if $image-size != null {
|
|
75
|
+
--hub-loading-image-size: #{$image-size};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// scss-docs-end hub-loading-theme
|