UNPKG

ngx-typed-writer

Version:

A Angular 2+ Typing Animation Library. Angular SSR Friendly

306 lines (299 loc) 18.1 kB
import * as i0 from '@angular/core'; import { inject, PLATFORM_ID, Renderer2, viewChild, model, input, booleanAttribute, output, ChangeDetectionStrategy, Component, NgModule } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; /* name of class with animation fade out */ const FADE_OUT_CLASS = 'typed-fade-out'; /** * The function `typeHtmlChars` is used to skip over HTML characters in a string if the `isHTML` flag * is set to true. * @param {boolean} isHTML - A boolean value indicating whether the current string contains HTML * characters. * @param {string} currentString - The current string that needs to be processed. It can contain HTML * characters that need to be skipped over. * @param {number} currentStringPosition - The current position in the string that is being processed. * @returns the updated value of `currentStringPosition`. */ function typeHtmlChars(isHTML, currentString, currentStringPosition) { if (!isHTML) return currentStringPosition; const currentCharacter = currentString .substring(currentStringPosition) .charAt(0); if (currentCharacter === '<' || currentCharacter === '&') { let endTag = ''; if (currentCharacter === '<') { endTag = '>'; } else { endTag = ';'; } while (currentString.substring(currentStringPosition + 1).charAt(0) !== endTag) { currentStringPosition++; if (currentStringPosition + 1 > currentString.length) { break; } } currentStringPosition++; } return currentStringPosition; } /** * The function `backSpaceHtmlChars` is a function that takes in a boolean `isHTML`, a * string `currentString`, and a number `currentStringPosition`, and returns a new number representing * the updated `currentStringPosition` after removing HTML characters. * @param {boolean} isHTML - A boolean value indicating whether the current string contains HTML * characters or not. * @param {string} currentString - The `currentString` parameter is a string that represents the * current text or HTML content. * @param {number} currentStringPosition - The current position within the current string. * @returns the updated value of `currentStringPosition`. */ function backSpaceHtmlChars(isHTML, currentString, currentStringPosition) { if (!isHTML) return currentStringPosition; const currentCharacter = currentString .substring(currentStringPosition) .charAt(0); if (currentCharacter === '>' || currentCharacter === ';') { let endTag = ''; if (currentCharacter === '>') { endTag = '<'; } else { endTag = '&'; } while (currentString.substring(currentStringPosition - 1).charAt(0) !== endTag) { currentStringPosition--; if (currentStringPosition < 0) { break; } } currentStringPosition--; } return currentStringPosition; } /** * The function `shuffleStringsIfNeeded` shuffles an array of strings if a boolean flag is set to true, * otherwise it returns the original array. * @param {boolean} shuffle - A boolean value indicating whether the strings should be shuffled or not. * @param {string[]} strings - An array of strings that need to be shuffled if the `shuffle` parameter * is set to `true`. * @returns an array of strings. If the `shuffle` parameter is `false`, it returns the original * `strings` array. If `shuffle` is `true`, it returns a shuffled version of the `strings` array. */ function shuffleStringsIfNeeded(shuffle, strings) { if (!shuffle) return strings; return strings.sort(() => Math.random() - 0.5); } class NgxTypedWriterComponent { constructor() { this.platformId = inject(PLATFORM_ID); this.renderer2 = inject(Renderer2); this.typedTextRef = viewChild('typedText', ...(ngDevMode ? [{ debugName: "typedTextRef" }] : [])); this.cursor = viewChild('cursorRef', ...(ngDevMode ? [{ debugName: "cursor" }] : [])); this.strings = model([], ...(ngDevMode ? [{ debugName: "strings" }] : [])); this.typeSpeed = input(40, ...(ngDevMode ? [{ debugName: "typeSpeed" }] : [])); this.startDelay = input(0, ...(ngDevMode ? [{ debugName: "startDelay" }] : [])); this.backSpeed = input(40, ...(ngDevMode ? [{ debugName: "backSpeed" }] : [])); this.smartBackspace = input(false, { ...(ngDevMode ? { debugName: "smartBackspace" } : {}), transform: booleanAttribute }); this.shuffle = input(false, { ...(ngDevMode ? { debugName: "shuffle" } : {}), transform: booleanAttribute }); this.backDelay = input(1000, ...(ngDevMode ? [{ debugName: "backDelay" }] : [])); this.isHTML = input(false, { ...(ngDevMode ? { debugName: "isHTML" } : {}), transform: booleanAttribute }); this.fadeOut = input(false, { ...(ngDevMode ? { debugName: "fadeOut" } : {}), transform: booleanAttribute }); this.loop = input(true, { ...(ngDevMode ? { debugName: "loop" } : {}), transform: booleanAttribute }); this.showCursor = input(true, { ...(ngDevMode ? { debugName: "showCursor" } : {}), transform: booleanAttribute }); this.cursorChar = input('|', ...(ngDevMode ? [{ debugName: "cursorChar" }] : [])); this.fadeOutDelay = input(500, ...(ngDevMode ? [{ debugName: "fadeOutDelay" }] : [])); this.currentStringIndex = 0; this.currentString = ''; this.currentStringPosition = 0; this.isTypingPaused = false; this.stopNum = 0; this.destroy = output(); this.initTyped = output(); this.completeLoop = output(); } ngOnInit() { if (isPlatformBrowser(this.platformId)) { this.init(); } } ngOnDestroy() { clearTimeout(this.timeout); this.destroy.emit(); } init() { this.strings.set(shuffleStringsIfNeeded(this.shuffle(), this.strings())); this.currentString = this.strings()[this.currentStringIndex]; this.timeout = setTimeout(() => { this.typeString(); this.initTyped.emit(); }, this.startDelay()); } typeString() { if (this.isTypingPaused) return; if (this.fadeOut()) { const typedElement = this.typedTextRef() ?.nativeElement; this.renderer2.removeClass(typedElement, FADE_OUT_CLASS); if (this.showCursor()) { const cursorElement = this.cursor()?.nativeElement; this.renderer2.removeClass(cursorElement, FADE_OUT_CLASS); } } if (this.currentStringPosition < this.currentString.length) { this.typeCharacter(); } else { this.isTypingPaused = true; this.timeout = setTimeout(() => { this.isTypingPaused = false; this.timeout = setTimeout(() => { this.backspaceString(); }, this.backDelay()); }, this.typeSpeed()); } } typeCharacter() { this.timeout = setTimeout(() => { this.currentStringPosition = typeHtmlChars(this.isHTML(), this.currentString, this.currentStringPosition); const nextString = this.currentString.substring(0, this.currentStringPosition + 1); const lastItem = this.strings().at(-1); this.typedTextRef().nativeElement.innerHTML = nextString; this.currentStringPosition++; if (nextString === lastItem && !this.loop()) { this.completeLoop.emit(); return; } this.typeString(); }, this.typeSpeed()); } backspaceString() { if (this.isTypingPaused) return; if (this.fadeOut()) { this.initFadeOut(); return; } if (this.currentStringPosition > this.stopNum) { this.backspaceCharacter(); } else { this.isTypingPaused = true; this.timeout = setTimeout(() => { this.isTypingPaused = false; this.currentStringIndex++; if (this.currentStringIndex >= this.strings().length) { if (this.loop()) { this.currentStringIndex = 0; // this.typeString(); } else { return; // Finished typing all strings } } this.currentString = this.strings()[this.currentStringIndex]; this.timeout = setTimeout(() => { this.typeString(); }, this.typeSpeed()); }, this.typeSpeed()); } } backspaceCharacter() { const currentString = this.typedTextRef()?.nativeElement.innerHTML; this.currentStringPosition = backSpaceHtmlChars(this.isHTML(), this.currentString, this.currentStringPosition); const curStringAtPosition = currentString.substring(0, this.currentStringPosition); this.typedTextRef().nativeElement.innerHTML = curStringAtPosition; this.timeout = setTimeout(() => { // if smartBack is enabled if (this.smartBackspace()) { // the remaining part of the current string is equal of the same part of the new string const nextStringPartial = this.strings()[this.currentStringIndex + 1]; const compare = curStringAtPosition === nextStringPartial?.substring(0, this.currentStringPosition); if (nextStringPartial && compare) { this.stopNum = this.currentStringPosition - 1; } else { this.stopNum = 0; } } if (this.currentStringPosition > this.stopNum) { // subtract characters one by one this.currentStringPosition--; // loop the function this.backspaceString(); } else if (this.currentStringPosition <= this.stopNum) { // if the stop number has been reached, increase // array position to next string this.currentStringIndex++; // When looping, begin at the beginning after backspace complete if (this.currentStringIndex === this.strings.length) { this.currentStringIndex = 0; } this.typeString(); } }, this.backSpeed()); } initFadeOut() { const typedElement = this.typedTextRef()?.nativeElement; this.renderer2.addClass(typedElement, FADE_OUT_CLASS); if (this.showCursor()) { const cursorElement = this.cursor()?.nativeElement; this.renderer2.addClass(cursorElement, FADE_OUT_CLASS); } this.timeout = setTimeout(() => { this.currentStringIndex++; typedElement.innerHTML = ''; // Resets current string if end of loop reached if (this.strings().length > this.currentStringIndex) { this.currentStringPosition = 0; this.currentString = this.strings()[this.currentStringIndex]; this.typeString(); } else { this.currentStringPosition = 0; this.currentStringIndex = 0; this.currentString = this.strings()[this.currentStringIndex]; this.typeString(); // this.currentStringIndex++; } }, this.fadeOutDelay()); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgxTypedWriterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NgxTypedWriterComponent, isStandalone: true, selector: "ngx-typed-writer", inputs: { strings: { classPropertyName: "strings", publicName: "strings", isSignal: true, isRequired: false, transformFunction: null }, typeSpeed: { classPropertyName: "typeSpeed", publicName: "typeSpeed", isSignal: true, isRequired: false, transformFunction: null }, startDelay: { classPropertyName: "startDelay", publicName: "startDelay", isSignal: true, isRequired: false, transformFunction: null }, backSpeed: { classPropertyName: "backSpeed", publicName: "backSpeed", isSignal: true, isRequired: false, transformFunction: null }, smartBackspace: { classPropertyName: "smartBackspace", publicName: "smartBackspace", isSignal: true, isRequired: false, transformFunction: null }, shuffle: { classPropertyName: "shuffle", publicName: "shuffle", isSignal: true, isRequired: false, transformFunction: null }, backDelay: { classPropertyName: "backDelay", publicName: "backDelay", isSignal: true, isRequired: false, transformFunction: null }, isHTML: { classPropertyName: "isHTML", publicName: "isHTML", isSignal: true, isRequired: false, transformFunction: null }, fadeOut: { classPropertyName: "fadeOut", publicName: "fadeOut", isSignal: true, isRequired: false, transformFunction: null }, loop: { classPropertyName: "loop", publicName: "loop", isSignal: true, isRequired: false, transformFunction: null }, showCursor: { classPropertyName: "showCursor", publicName: "showCursor", isSignal: true, isRequired: false, transformFunction: null }, cursorChar: { classPropertyName: "cursorChar", publicName: "cursorChar", isSignal: true, isRequired: false, transformFunction: null }, fadeOutDelay: { classPropertyName: "fadeOutDelay", publicName: "fadeOutDelay", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { strings: "stringsChange", destroy: "destroy", initTyped: "initTyped", completeLoop: "completeLoop" }, viewQueries: [{ propertyName: "typedTextRef", first: true, predicate: ["typedText"], descendants: true, isSignal: true }, { propertyName: "cursor", first: true, predicate: ["cursorRef"], descendants: true, isSignal: true }], ngImport: i0, template: ` <span #typedText> </span> @if (showCursor()) { <span #cursorRef class="typing-cursor">{{ cursorChar() }} </span> } `, isInline: true, styles: [".typing-cursor{-webkit-animation:blink .7s infinite;display:inline-block;opacity:1;animation:blink .7s infinite}@keyframes blink{0%{opacity:1}50%{opacity:0}to{opacity:1}}.typed-fade-out{opacity:0;transition:opacity .25s}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgxTypedWriterComponent, decorators: [{ type: Component, args: [{ selector: 'ngx-typed-writer', changeDetection: ChangeDetectionStrategy.OnPush, template: ` <span #typedText> </span> @if (showCursor()) { <span #cursorRef class="typing-cursor">{{ cursorChar() }} </span> } `, styles: [".typing-cursor{-webkit-animation:blink .7s infinite;display:inline-block;opacity:1;animation:blink .7s infinite}@keyframes blink{0%{opacity:1}50%{opacity:0}to{opacity:1}}.typed-fade-out{opacity:0;transition:opacity .25s}\n"] }] }], propDecorators: { typedTextRef: [{ type: i0.ViewChild, args: ['typedText', { isSignal: true }] }], cursor: [{ type: i0.ViewChild, args: ['cursorRef', { isSignal: true }] }], strings: [{ type: i0.Input, args: [{ isSignal: true, alias: "strings", required: false }] }, { type: i0.Output, args: ["stringsChange"] }], typeSpeed: [{ type: i0.Input, args: [{ isSignal: true, alias: "typeSpeed", required: false }] }], startDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "startDelay", required: false }] }], backSpeed: [{ type: i0.Input, args: [{ isSignal: true, alias: "backSpeed", required: false }] }], smartBackspace: [{ type: i0.Input, args: [{ isSignal: true, alias: "smartBackspace", required: false }] }], shuffle: [{ type: i0.Input, args: [{ isSignal: true, alias: "shuffle", required: false }] }], backDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "backDelay", required: false }] }], isHTML: [{ type: i0.Input, args: [{ isSignal: true, alias: "isHTML", required: false }] }], fadeOut: [{ type: i0.Input, args: [{ isSignal: true, alias: "fadeOut", required: false }] }], loop: [{ type: i0.Input, args: [{ isSignal: true, alias: "loop", required: false }] }], showCursor: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCursor", required: false }] }], cursorChar: [{ type: i0.Input, args: [{ isSignal: true, alias: "cursorChar", required: false }] }], fadeOutDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "fadeOutDelay", required: false }] }], destroy: [{ type: i0.Output, args: ["destroy"] }], initTyped: [{ type: i0.Output, args: ["initTyped"] }], completeLoop: [{ type: i0.Output, args: ["completeLoop"] }] } }); class NgxTypedWriterModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgxTypedWriterModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.0.6", ngImport: i0, type: NgxTypedWriterModule, imports: [NgxTypedWriterComponent], exports: [NgxTypedWriterComponent] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgxTypedWriterModule }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NgxTypedWriterModule, decorators: [{ type: NgModule, args: [{ imports: [NgxTypedWriterComponent], exports: [NgxTypedWriterComponent], }] }] }); /** * Generated bundle index. Do not edit. */ export { NgxTypedWriterComponent, NgxTypedWriterModule }; //# sourceMappingURL=ngx-typed-writer.mjs.map