ngx-typed-writer
Version:
A Angular 2+ Typing Animation Library. Angular SSR Friendly
306 lines (299 loc) • 18.1 kB
JavaScript
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>
(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>
(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