UNPKG

ngx-skeleton-loader

Version:

Make beautiful, animated loading skeletons that automatically adapt to your Angular apps

158 lines (150 loc) 14.7 kB
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