ngx-skltn
Version:
Skeleton loader for Angular
317 lines (306 loc) • 12.5 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, Injectable, Inject, Component, ElementRef, ChangeDetectorRef, NgZone, Input, HostListener, Directive, ContentChild, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DomSanitizer } from '@angular/platform-browser';
import { BehaviorSubject, Subject } from 'rxjs';
import { debounceTime, tap, filter, takeUntil, throttleTime } from 'rxjs/operators';
const SKLTN_CONFIG_TOKEN = new InjectionToken('SkltnConfig');
class SkltnService {
constructor(config) {
this.ids = [];
this.defaultConfig = {
rectRadius: 4,
bgFill: '#ddd',
flareFill: 'rgba(255, 255, 255, 0.6)',
flareWidth: '150px',
duration: 1200,
delay: 0,
timing: 'ease-in-out',
};
this.config = Object.assign({}, this.defaultConfig, config);
}
}
SkltnService.ɵprov = i0.ɵɵdefineInjectable({ factory: function SkltnService_Factory() { return new SkltnService(i0.ɵɵinject(SKLTN_CONFIG_TOKEN)); }, token: SkltnService, providedIn: "root" });
SkltnService.decorators = [
{ type: Injectable, args: [{
providedIn: 'root'
},] }
];
SkltnService.ctorParameters = () => [
{ type: undefined, decorators: [{ type: Inject, args: [SKLTN_CONFIG_TOKEN,] }] }
];
function generateId() {
return Math.random().toString(32).substr(2, 4);
}
class BonesList {
constructor() {
this.changes = new BehaviorSubject([]);
this.map = new Map();
}
add(bone) {
const id = generateId();
this.map.set(id, bone);
this.emit();
return id;
}
remove(id) {
this.map.delete(id);
this.emit();
}
emit() {
const list = this.getList();
this.changes.next(list);
}
getList() {
const values = this.map.values();
return Array.from(values);
}
}
BonesList.decorators = [
{ type: Injectable }
];
BonesList.ctorParameters = () => [];
class SkltnComponent {
constructor(skltnService, element, sanitizer, cd, zone, bonesList) {
this.skltnService = skltnService;
this.element = element;
this.sanitizer = sanitizer;
this.cd = cd;
this.zone = zone;
this.bonesList = bonesList;
this.showSkltn = true;
this.checkInterval = 100;
this.updStream$ = new Subject();
this.checkStream$ = new Subject();
this.unsubscribe$ = new Subject();
this.defaultConfig = {
rectRadius: 4,
bgFill: '#ddd',
flareFill: 'rgba(255, 255, 255, 0.6)',
flareWidth: '150px',
duration: 1200,
delay: 0,
timing: 'ease-in-out',
};
const conf = this.skltnService.config;
this.rectRadius = conf.rectRadius;
this.bgFill = conf.bgFill;
this.flareFill = conf.flareFill;
this.flareWidth = conf.flareWidth;
this.duration = conf.duration;
this.delay = conf.delay;
this.timing = conf.timing;
this.sufix = this.getSufixWithID();
this.gradientId = 'gradient-' + this.sufix;
this.maskId = 'mask-' + this.sufix;
}
onResize(event) {
this.updStream$.next();
}
ngOnInit() {
this.animationCss = this.sanitizer.bypassSecurityTrustHtml(`
<style>
skltnFlareAnimation {
0% { x: calc(0% - ${this.flareWidth}); }
100% { x: 100%; }
}
.skltn-flare {
animation-name: skltnFlareAnimation;
animation-duration: ${this.duration}ms;
animation-timing-function: ${this.timing};
animation-iteration-count: infinite;
animation-delay: ${this.delay}ms;
}
</style>`);
// Bones Updates
this.bonesList.changes.pipe(debounceTime(50), tap(list => {
this.bones = list;
this.calcShapes();
this.cd.detectChanges();
}), filter((list, index) => index === 0), tap(() => {
this.checkStream$.next();
}), takeUntil(this.unsubscribe$)).subscribe();
// Update
this.updStream$.pipe(throttleTime(50), takeUntil(this.unsubscribe$)).subscribe(() => this.calcShapes());
// Update href (Safari Bug, SVG Ref Path)
this.href = window.location.href;
this.zone.runOutsideAngular(() => {
this.checkStream$.pipe(tap(() => {
// Check if href changed
if (this.href !== window.location.href) {
this.href = window.location.href;
this.cd.detectChanges();
}
// Check if root element resized
const el = this.element.nativeElement;
const { width, height } = el.getBoundingClientRect();
if (this.parentClientRect.width !== width || this.parentClientRect.height !== height) {
this.updStream$.next();
this.cd.detectChanges();
}
setTimeout(() => {
this.checkStream$.next();
}, this.checkInterval);
}), takeUntil(this.unsubscribe$)).subscribe();
});
}
ngAfterViewInit() {
}
ngOnDestroy() {
this.unsubscribe$.next();
this.unsubscribe$.complete();
}
getSufixWithID() {
return generateId() + '-skltn';
}
calcShapes() {
var _a;
// Root SVG Element
const el = this.element.nativeElement;
this.parentClientRect = el.getBoundingClientRect();
this.viewBox = `0 0 ${this.parentClientRect.width} ${this.parentClientRect.height}`;
// SVG Shapes
if (!((_a = this.bones) === null || _a === void 0 ? void 0 : _a.length)) {
this.shapes = null;
return;
}
this.shapes = this.bones.map(bone => {
const boneEl = bone.element.nativeElement;
const clientRect = boneEl.getBoundingClientRect();
const radius = bone.rectRadius || this.rectRadius;
if (bone.type === 'circle') {
return {
type: 'circle',
cx: clientRect.x - this.parentClientRect.x + clientRect.width / 2,
cy: clientRect.y - this.parentClientRect.y + clientRect.height / 2,
rx: clientRect.width / 2,
ry: clientRect.height / 2,
};
}
if (bone.type === 'path') {
const x = clientRect.x - this.parentClientRect.x;
const y = clientRect.y - this.parentClientRect.y;
let transform = `translate(${x}, ${y})`;
if (bone.pathWidth && bone.pathHeight) {
const xScale = (clientRect.width / bone.pathWidth).toFixed(2);
const yScale = (clientRect.height / bone.pathHeight).toFixed(2);
transform += ` scale(${xScale}, ${yScale})`;
}
return {
type: 'path',
width: clientRect.width,
height: clientRect.height,
transform,
template: bone.template,
};
}
return {
type: 'rect',
x: clientRect.x - this.parentClientRect.x,
y: clientRect.y - this.parentClientRect.y,
width: clientRect.width,
height: clientRect.height,
rx: radius,
ry: radius,
};
});
}
}
SkltnComponent.decorators = [
{ type: Component, args: [{
selector: 'skltn-root',
template: "<div [innerHTML]=\"animationCss\"></div>\n\n<svg class=\"svg-root\" x=\"0px\" y=\"0px\" [attr.viewBox]=\"viewBox\" *ngIf=\"shapes?.length && showSkltn\">\n\n <!-- Gradient -->\n <linearGradient [attr.id]=\"gradientId\">\n <stop class=\"stop stop--1\" [attr.stop-color]=\"flareFill\" stop-opacity=\"0\" offset=\"0\"></stop>\n <stop class=\"stop stop--2\" [attr.stop-color]=\"flareFill\" stop-opacity=\"1\" offset=\"50%\"></stop>\n <stop class=\"stop stop--3\" [attr.stop-color]=\"flareFill\" stop-opacity=\"0\" offset=\"100%\"></stop>\n </linearGradient>\n\n <defs>\n <!-- Mask -->\n <mask [attr.id]=\"maskId\">\n <!-- Shapes -->\n <ng-container *ngFor=\"let shape of shapes\">\n <!-- Rect Type -->\n <rect *ngIf=\"shape.type === 'rect'\" [attr.x]=\"shape.x\" [attr.y]=\"shape.y\" [attr.rx]=\"shape.rx\"\n [attr.ry]=\"shape.ry\" [attr.width]=\"shape.width\" [attr.height]=\"shape.height\" fill=\"#FFFFFF\" />\n <!-- Circle Type -->\n <ellipse *ngIf=\"shape.type === 'circle'\" [attr.cx]=\"shape.cx\" [attr.cy]=\"shape.cy\" [attr.rx]=\"shape.rx\" [attr.ry]=\"shape.ry\" fill=\"#FFFFFF\"></ellipse>\n <!-- Path Type -->\n <ng-container *ngIf=\"shape.type === 'path'\">\n <g fill=\"#FFFFFF\" [attr.transform]=\"shape.transform\">\n <ng-container *ngTemplateOutlet=\"shape.template\"></ng-container>\n </g>\n </ng-container>\n </ng-container>\n </mask>\n </defs>\n\n <g [attr.mask]=\"'url(' + href + '#' + maskId + ')'\">\n <!-- Background Rectangle -->\n <rect class=\"bg-fill\" x=\"0\" y=\"0\" width=\"100%\" height=\"100%\" [attr.fill]=\"bgFill\"></rect>\n\n <!-- Flare -->\n <rect class=\"skltn-flare\" [attr.x]=\"'-' + flareWidth\" y=\"0\" [attr.width]=\"flareWidth\" height=\"100%\" [attr.fill]=\"'url(' + href + '#' + gradientId + ')'\"></rect>\n </g>\n\n</svg>\n\n<ng-content></ng-content>\n",
providers: [{
provide: BonesList,
useClass: BonesList,
}],
styles: [":host{display:block;position:relative}.svg-root{display:block;position:absolute;top:0;bottom:0;left:0;right:0;z-index:1}"]
},] }
];
SkltnComponent.ctorParameters = () => [
{ type: SkltnService },
{ type: ElementRef },
{ type: DomSanitizer },
{ type: ChangeDetectorRef },
{ type: NgZone },
{ type: BonesList }
];
SkltnComponent.propDecorators = {
rectRadius: [{ type: Input }],
bgFill: [{ type: Input }],
flareFill: [{ type: Input }],
flareWidth: [{ type: Input }],
duration: [{ type: Input }],
delay: [{ type: Input }],
timing: [{ type: Input }],
showSkltn: [{ type: Input }],
checkInterval: [{ type: Input }],
onResize: [{ type: HostListener, args: ['window:resize', ['$event'],] }]
};
class SkltnBoneDirective {
constructor(element, bonesList) {
this.element = element;
this.bonesList = bonesList;
}
ngOnInit() {
this.id = this.bonesList.add(this);
}
ngOnDestroy() {
this.bonesList.remove(this.id);
}
}
SkltnBoneDirective.decorators = [
{ type: Directive, args: [{
selector: '[skltn-bone]',
},] }
];
SkltnBoneDirective.ctorParameters = () => [
{ type: ElementRef },
{ type: BonesList }
];
SkltnBoneDirective.propDecorators = {
type: [{ type: Input }],
rectRadius: [{ type: Input }],
pathWidth: [{ type: Input }],
pathHeight: [{ type: Input }],
template: [{ type: ContentChild, args: ['boneTemp', { static: true },] }]
};
class NgxSkltnModule {
static forRoot(config = {}) {
return {
ngModule: NgxSkltnModule,
providers: [{
provide: SKLTN_CONFIG_TOKEN,
useValue: config,
},
{
provide: SkltnService,
useClass: SkltnService,
deps: [SKLTN_CONFIG_TOKEN],
}]
};
}
}
NgxSkltnModule.decorators = [
{ type: NgModule, args: [{
declarations: [
SkltnComponent,
SkltnBoneDirective,
],
imports: [
CommonModule,
],
exports: [
SkltnComponent,
SkltnBoneDirective,
]
},] }
];
/*
* Public API Surface of ngx-skltn
*/
/**
* Generated bundle index. Do not edit.
*/
export { NgxSkltnModule, SKLTN_CONFIG_TOKEN, SkltnBoneDirective, SkltnComponent, SkltnService, BonesList as ɵa };
//# sourceMappingURL=ngx-skltn.js.map