ngx-skeleton-loader
Version:
Make beautiful, animated loading skeletons that automatically adapt to your Angular apps
158 lines (150 loc) • 14.7 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, inject, input, numberAttribute, computed, isDevMode, ChangeDetectionStrategy, Component, NgModule, makeEnvironmentProviders } from '@angular/core';
const NGX_SKELETON_LOADER_CONFIG = new InjectionToken('ngx-skeleton-loader.config');
/**
* The `NgxSkeletonLoaderComponent` is a standalone Angular component that provides a skeleton
* loader UI element.
* It can be used to display a loading state before the actual content is available.
* The component can be configured with various options such as the number of elements, appearance,
* animation, and theme.
*/
class NgxSkeletonLoaderComponent {
constructor() {
/**
* Injects the `NgxSkeletonLoaderConfig` configuration object, which is optional.
* This configuration object provides various options for customizing the behavior
* and appearance of the `NgxSkeletonLoaderComponent`.
*/
this.#config = inject(NGX_SKELETON_LOADER_CONFIG, { optional: true });
/**
* The `count` property is an input that determines the number of skeleton loader elements
* to display.
* It is initialized with the value from the `NgxSkeletonLoaderConfig` object, or 1 if the config
* is not provided.
* The `transform: numberAttribute` option ensures that the input value is converted to a number.
*/
this.count = input(this.#config?.count || 1, { transform: numberAttribute });
/**
* The `loadingText` property is an input that determines the text to display while the content
* is loading.
* It is initialized with the value from the `NgxSkeletonLoaderConfig` object, or 'Loading...'
* if the config is not provided.
*/
this.loadingText = input(this.#config?.loadingText || 'Loading...');
/**
* The `appearance` property is an input that determines the visual appearance of the skeleton
* loader.
* It is initialized with the value from the `NgxSkeletonLoaderConfig` object, or 'line' if the
* config is not provided.
* The available appearance options are defined in the `NgxSkeletonLoaderConfig['appearance']`
* type.
*/
this.appearance = input(this.#config?.appearance || 'line');
/**
* The `animation` property is an input that determines the type of animation to apply to the
* skeleton loader.
* It is initialized with the value from the `NgxSkeletonLoaderConfig` object, or 'progress' if
* the config is not provided.
* The available animation options are defined in the `NgxSkeletonLoaderConfig['animation']` type.
*/
this.animation = input(this.#config?.animation || 'progress');
/**
* The `ariaLabel` property is an input that determines the ARIA label to be used for the skeleton
* loader element. This is useful for providing accessibility information to screen readers.
* It is initialized with the value from the `NgxSkeletonLoaderConfig` object, or 'loading' if the
* config is not provided.
*/
this.ariaLabel = input(this.#config?.ariaLabel || 'loading');
/**
* The `theme` property is an input that determines the theme configuration for the skeleton
* loader.
* It is initialized with the value from the `NgxSkeletonLoaderConfig` object, or `null` if the
* config is not provided.
* The theme configuration is defined by the `NgxSkeletonLoaderConfigTheme` type, which allows
* customizing various aspects of the skeleton loader's appearance, such as colors, animation,
* etc.
*/
this.theme = input(this.#config?.theme || null);
/**
* The `items` property is a computed property that generates an array of indices based on the
* `count` input.
* If the `appearance` is set to 'custom-content', the `count` is forced to 1 to ensure that the
* skeleton loader is displayed as a single DOM node, as required by the 'custom-content'
* appearance.
* This computed property is used to render the appropriate number of skeleton loader elements.
*/
this.items = computed(() => {
let count = this.count() || 1;
// Force count to 1 when custom-content is used
if (this.appearance() === 'custom-content') {
// Shows error message only in Development
if (isDevMode() && count !== 1) {
// eslint-disable-next-line no-console
console.error(`\`NgxSkeletonLoaderComponent\` enforces elements with "custom-content" appearance as DOM nodes. Forcing "count" to "1".`);
count = 1;
}
}
return [...Array(count)].map((_, index) => index);
});
/**
* A computed property that returns the final theme configuration for the skeleton loader.
* If the `extendsFromRoot` property is set in the `NgxSkeletonLoaderConfig`, the theme is merged
* with the root theme configuration. Otherwise, the theme is returned as-is.
* This allows the skeleton loader to inherit global theme settings while still allowing for
* component-specific theme customization.
*/
this.styles = computed(() => {
const theme = this.theme();
if (this.#config?.theme?.extendsFromRoot) {
return {
...this.#config?.theme,
...theme,
};
}
return theme;
});
}
/**
* Injects the `NgxSkeletonLoaderConfig` configuration object, which is optional.
* This configuration object provides various options for customizing the behavior
* and appearance of the `NgxSkeletonLoaderComponent`.
*/
#config;
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: NgxSkeletonLoaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.2", type: NgxSkeletonLoaderComponent, isStandalone: true, selector: "ngx-skeleton-loader", inputs: { count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, animation: { classPropertyName: "animation", publicName: "animation", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@let appearanceValue = appearance();\n@let animationValue = animation();\n@for (item of items(); track item) {\n <div\n class=\"skeleton-loader\"\n [attr.aria-label]=\"ariaLabel()\"\n aria-busy=\"true\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n [attr.aria-valuetext]=\"loadingText()\"\n role=\"progressbar\"\n tabindex=\"-1\"\n [class.custom-content]=\"appearanceValue === 'custom-content'\"\n [class.circle]=\"appearanceValue === 'circle'\"\n [class.progress]=\"animationValue === 'progress'\"\n [class.progress-dark]=\"animationValue === 'progress-dark'\"\n [class.pulse]=\"animationValue === 'pulse'\"\n [class.pulse-dark]=\"animationValue === 'pulse-dark'\"\n [style]=\"styles()\"\n >\n @if (appearanceValue === 'custom-content') {\n <ng-content></ng-content>\n }\n </div>\n}\n", styles: [".skeleton-loader{box-sizing:border-box;overflow:hidden;position:relative;background:#eff1f6 no-repeat;border-radius:4px;width:100%;height:20px;display:inline-block;margin-bottom:10px;will-change:transform}.skeleton-loader:after,.skeleton-loader:before{box-sizing:border-box}.skeleton-loader.circle{width:40px;height:40px;margin:5px;border-radius:50%}.skeleton-loader.progress,.skeleton-loader.progress-dark{transform:translateZ(0)}.skeleton-loader.progress:after,.skeleton-loader.progress:before,.skeleton-loader.progress-dark:after,.skeleton-loader.progress-dark:before{box-sizing:border-box}.skeleton-loader.progress:before,.skeleton-loader.progress-dark:before{animation:progress 2s ease-in-out infinite;background-size:200px 100%;position:absolute;z-index:1;top:0;left:0;width:200px;height:100%;content:\"\"}.skeleton-loader.progress:before{background-image:linear-gradient(90deg,#fff0,#fff9,#fff0)}.skeleton-loader.progress-dark:before{background-image:linear-gradient(90deg,transparent,rgba(0,0,0,.2),transparent)}.skeleton-loader.pulse{animation:pulse 1.5s cubic-bezier(.4,0,.2,1) infinite;animation-delay:.5s}.skeleton-loader.pulse-dark{background:#32323233;animation:pulse 1.5s cubic-bezier(.4,0,.2,1) infinite;animation-delay:.5s}.skeleton-loader.custom-content{height:100%;background:none}@media (prefers-reduced-motion: reduce){.skeleton-loader.pulse,.skeleton-loader.progress-dark,.skeleton-loader.pulse-dark,.skeleton-loader.custom-content,.skeleton-loader.progress:before{animation:none}.skeleton-loader.progress:before,.skeleton-loader.progress-dark,.skeleton-loader.pulse-dark,.skeleton-loader.custom-content{background-image:none}}@media screen and (min-device-width: 1200px){.skeleton-loader{-webkit-user-select:none;user-select:none;cursor:wait}}@keyframes progress{0%{transform:translate3d(-200px,0,0)}to{transform:translate3d(calc(200px + 100vw),0,0)}}@keyframes pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: NgxSkeletonLoaderComponent, decorators: [{
type: Component,
args: [{ selector: 'ngx-skeleton-loader', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "@let appearanceValue = appearance();\n@let animationValue = animation();\n@for (item of items(); track item) {\n <div\n class=\"skeleton-loader\"\n [attr.aria-label]=\"ariaLabel()\"\n aria-busy=\"true\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n [attr.aria-valuetext]=\"loadingText()\"\n role=\"progressbar\"\n tabindex=\"-1\"\n [class.custom-content]=\"appearanceValue === 'custom-content'\"\n [class.circle]=\"appearanceValue === 'circle'\"\n [class.progress]=\"animationValue === 'progress'\"\n [class.progress-dark]=\"animationValue === 'progress-dark'\"\n [class.pulse]=\"animationValue === 'pulse'\"\n [class.pulse-dark]=\"animationValue === 'pulse-dark'\"\n [style]=\"styles()\"\n >\n @if (appearanceValue === 'custom-content') {\n <ng-content></ng-content>\n }\n </div>\n}\n", styles: [".skeleton-loader{box-sizing:border-box;overflow:hidden;position:relative;background:#eff1f6 no-repeat;border-radius:4px;width:100%;height:20px;display:inline-block;margin-bottom:10px;will-change:transform}.skeleton-loader:after,.skeleton-loader:before{box-sizing:border-box}.skeleton-loader.circle{width:40px;height:40px;margin:5px;border-radius:50%}.skeleton-loader.progress,.skeleton-loader.progress-dark{transform:translateZ(0)}.skeleton-loader.progress:after,.skeleton-loader.progress:before,.skeleton-loader.progress-dark:after,.skeleton-loader.progress-dark:before{box-sizing:border-box}.skeleton-loader.progress:before,.skeleton-loader.progress-dark:before{animation:progress 2s ease-in-out infinite;background-size:200px 100%;position:absolute;z-index:1;top:0;left:0;width:200px;height:100%;content:\"\"}.skeleton-loader.progress:before{background-image:linear-gradient(90deg,#fff0,#fff9,#fff0)}.skeleton-loader.progress-dark:before{background-image:linear-gradient(90deg,transparent,rgba(0,0,0,.2),transparent)}.skeleton-loader.pulse{animation:pulse 1.5s cubic-bezier(.4,0,.2,1) infinite;animation-delay:.5s}.skeleton-loader.pulse-dark{background:#32323233;animation:pulse 1.5s cubic-bezier(.4,0,.2,1) infinite;animation-delay:.5s}.skeleton-loader.custom-content{height:100%;background:none}@media (prefers-reduced-motion: reduce){.skeleton-loader.pulse,.skeleton-loader.progress-dark,.skeleton-loader.pulse-dark,.skeleton-loader.custom-content,.skeleton-loader.progress:before{animation:none}.skeleton-loader.progress:before,.skeleton-loader.progress-dark,.skeleton-loader.pulse-dark,.skeleton-loader.custom-content{background-image:none}}@media screen and (min-device-width: 1200px){.skeleton-loader{-webkit-user-select:none;user-select:none;cursor:wait}}@keyframes progress{0%{transform:translate3d(-200px,0,0)}to{transform:translate3d(calc(200px + 100vw),0,0)}}@keyframes pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}\n"] }]
}] });
class NgxSkeletonLoaderModule {
static forRoot(config) {
return {
ngModule: NgxSkeletonLoaderModule,
providers: [{ provide: NGX_SKELETON_LOADER_CONFIG, useValue: config }],
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: NgxSkeletonLoaderModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.0.2", ngImport: i0, type: NgxSkeletonLoaderModule, imports: [NgxSkeletonLoaderComponent], exports: [NgxSkeletonLoaderComponent] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: NgxSkeletonLoaderModule }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.2", ngImport: i0, type: NgxSkeletonLoaderModule, decorators: [{
type: NgModule,
args: [{
imports: [NgxSkeletonLoaderComponent],
exports: [NgxSkeletonLoaderComponent],
}]
}] });
function provideNgxSkeletonLoader(config) {
return makeEnvironmentProviders([
{ provide: NGX_SKELETON_LOADER_CONFIG, useValue: config },
]);
}
/*
* Public API Surface of ngx-skeleton-loader
*/
/**
* Generated bundle index. Do not edit.
*/
export { NGX_SKELETON_LOADER_CONFIG, NgxSkeletonLoaderComponent, NgxSkeletonLoaderModule, provideNgxSkeletonLoader };
//# sourceMappingURL=ngx-skeleton-loader.mjs.map