typing-effect-ts
Advanced tools
Comparing version 1.3.5 to 1.4.0
@@ -199,4 +199,12 @@ type CursorSymbols = { | ||
onArrayFinished: (callback: () => void, once?: boolean | undefined) => () => void; | ||
/** | ||
* Registers a callback that will be called when instance is being disposed right before setting `instanceState` to `disposed`. | ||
* @param callback - A function to be called at disposal. | ||
* | ||
* @returns A function that removes the callback | ||
* @throws If provided `callback` is not a function; if called after `dispose` | ||
*/ | ||
onInstanceDisposed: (callback: () => void) => () => void; | ||
} | ||
export { TypingEffect }; |
@@ -1,2 +0,2 @@ | ||
function*y(a,t){if(t){let i=a.length;for(;i>=0;)yield a.substring(0,i),i--}else{let i=1;for(;a.length>=i;)yield a.substring(0,i),i++}}function*u(a,t){let i=t?.rewindStringOnFinish,e=t?.startAtString??0,n=e<a.length&&e>0?e:0;for(;a.length>n;){let r=a[n]||"",s=y(r),l=s.next();for(;!l.done;)yield{string:l.value,index:n},l=s.next();if(yield{stringEnd:!0},i){let o=y(r,!0),h=o.next();for(;!h.done;)yield{string:h.value,index:n},h=o.next();yield{stringRewindEnd:!0}}n++}}function c(a,t=0){let i=performance.now(),e={rafId:0};function n(r){let s=r-i;s=s>=0?s:0,s>t&&(i=r,a(r)),e.rafId=requestAnimationFrame(n)}return e.rafId=requestAnimationFrame(n),e}var d=(a,t,i)=>{if(JSON.stringify(t,(e,n)=>n===void 0?"und":n)==="{}")return a;{let e={};for(let n in a)if(a.hasOwnProperty(n))if(i.hasOwnProperty(n))if(t.hasOwnProperty(n)){let r=a[n],s=t[n],l=i[n];if(s===void 0)e[n]=l;else switch(!0){case(Array.isArray(r)&&Array.isArray(s)):{e[n]=structuredClone(s);break}case(typeof r=="object"&&r!==null&&typeof s=="object"&&s!==null):{e[n]=d(r,s,l);break}default:{e[n]=s;break}}}else e[n]=a[n];else throw new Error(`Structure mismatch error. The "default object" does not have property "${n}" of the "current object".`);return e}};var p=()=>({typingDelay:100,untypingDelay:30,delayBeforeTyping:1600,delayAfterTyping:3e3,untypeString:!0,typingVariation:100,showCursor:!0,cursorSymbol:{typing:"|",untyping:"|",blinking:"|"},cursorBlinkRate:500,loop:!0}),g=class{#e=p();#k=()=>({...this.#e,cursorSymbol:{...this.#e.cursorSymbol}});get options(){return this.#k()}#n={iterator:null,initialResult:{done:!1,value:{string:"",index:0}},lastResult:{done:!1,value:{string:"",index:0}}};#p=t=>{this.#n.iterator=t,this.#n.lastResult=this.#n.initialResult};#x=t=>{this.#n.lastResult=t,t.value&&"string"in t.value&&(this.#a.value=t.value.string,this.#a.index=t.value.index,this.#a.nextExpectedIndex=t.value.index+1<this.#o.length?t.value.index+1:0)};#o=[];get strings(){return[...this.#o]}#A=(t,i)=>{if(!Array.isArray(t))throw new Error("Provided strings is not an array.");let e=()=>{this.#o=t,this.#o.length>0?this.#p(u(this.#o,{rewindStringOnFinish:this.#e.untypeString})):this.#p(null)},n=()=>{e(),this.#t=this.#c(),this.#i=null,this.#s=null},r=()=>{e(),this.#t=this.#c();let o=this.#m();this.#t!=="initialized"?(this.#t="running",this.#i=null,this.#s=null):(this.#i=null,this.#s=o)},s=()=>{e(),this.#t=this.#c();let o=this.#m("cycleStart");this.#t==="running"?(this.#i=o,this.#s=null):(this.#i=null,this.#s=null,this.#r=o)},l=()=>{i?s():this.#u("cycleStart",r,!0)};this.instanceState==="running"?l():n()};#y=null;get callback(){return this.#y}#C=(t,i)=>{if(t!==null&&typeof t!="function")throw new Error("Provided callback is not a function or null.");let e=()=>{this.#y=t,this.#t=this.#c(),this.#i=null,this.#s=null},n=()=>{this.#y=t,this.#t=this.#c();let l=this.#m();this.#t!=="initialized"?(this.#t="running",this.#i=null,this.#s=null):(this.#i=null,this.#s=l)},r=()=>{this.#y=t,this.#t=this.#c();let l=this.#m();this.#t==="running"?(this.#i=null,this.#s=null):(this.#i=null,this.#s=null,this.#r=l)},s=()=>{i?r():this.#u("cycleStart",n,!0)};this.instanceState==="running"?s():e()};#P=t=>{let i=this.#k(),e=p();if(t){let{cursorSymbol:n,...r}=t,s=r;if(t.hasOwnProperty("cursorSymbol"))switch(!0){case n===void 0:s.cursorSymbol={typing:void 0,untyping:void 0,blinking:void 0};break;case typeof n=="object":s.cursorSymbol=n;break;default:s.cursorSymbol={typing:n,untyping:n,blinking:n};break}return d(i,s,e)}else return i};#E=(t,i)=>{if(t!==void 0&&(typeof t!="object"||t===null))throw new Error("Provided options is not an object.");let e=()=>{let o=this.#P(t),h=!1;return this.#e.untypeString!==o.untypeString&&(this.#p(u(this.#o,{rewindStringOnFinish:o.untypeString})),h=!0),this.#e=o,h},n=()=>{e()},r=()=>{e()},s=()=>{let o=e(),h=this.#m("cycleStart");o&&(this.#r=h,this.#i=null,this.#s=null)},l=()=>{i?s():this.#u("cycleStart",r,!0)};this.instanceState==="running"?l():n()};#S={visible:!1,lastChangeTimestamp:0};#R=c(t=>{this.#t==="running"&&this.#v&&(!this.#n.lastResult.done||this.#r==="idle"?(this.#i&&(this.#r=this.#i,this.#i=null),this.#r=this.#v[this.#r].handle(t),this.#s&&(this.#r=this.#s,this.#s=null)):(this.#F("arrayFinished"),this.#e.loop?(this.#p(u(this.#o,{rewindStringOnFinish:this.#e.untypeString})),this.#r="cycleStart"):this.#r="idle"))});#a={value:"",index:0,nextExpectedIndex:0,changeTimestamp:0,callbackString:""};#t="initialized";get instanceState(){return this.#t}#r;get runningState(){return this.#r}#i=null;#s=null;#d=null;#v=null;#w=!1;#O=()=>({cycleStart:[],idle:[],beforeTyping:[],typing:[],afterTyping:[],delayAfterTyping:[],beforeUntyping:[],untyping:[],afterUntyping:[],delayBeforeTyping:[],arrayFinished:[]});#f=this.#O();#u=(t,i,e)=>{let n=Symbol("cbId");return this.#f[t].push({callback:i,once:e,called:!1,id:n,remove:!1}),n};#b=(t,i)=>{let e=this.#f[t].find(({id:n})=>n===i);e&&(e.remove=!0)};#F=t=>{if(this.#t!=="disposed"){let i=[];this.#f[t].forEach((e,n)=>{(e.once||e.remove)&&i.unshift(n),!e.called&&!e.remove&&(e.callback(),e.called=!0)}),i.forEach(e=>{this.#f[t].splice(e,1)}),this.#f[t].forEach(e=>{e.called=!1})}};#g=0;#h=(t,i,e)=>({[t]:{handle:n=>{this.#F(t);let r=typeof i=="function"?i():i;return e?e(n)==="done"?(this.#g=n,r):t:(this.#g=n,r)}}});#T=t=>{typeof this.#y=="function"&&t!==this.#a.callbackString&&(this.#y(t),this.#a.callbackString=t)};#I(t,i=this.#a.value){return t>=Math.max(this.#a.changeTimestamp,this.#g,this.#S.lastChangeTimestamp)+this.#e.cursorBlinkRate&&(this.#S.lastChangeTimestamp=t,this.#S.visible=!this.#S.visible),i+(this.#S.visible?this.#e.cursorSymbol.blinking:"")}#l=(t,i,e,n)=>(...r)=>{let s=()=>e==="any"?!0:Array.isArray(e)?e.includes(this.#t):this.#t===e;if(this.#t==="disposed")throw new Error(`The method "${i}" could not be called - the instance of TypingEffect was disposed. Create a new one.`);let l=Array.isArray(e)?e.join(", "):e;if(s()){let h=t.apply(this,r);return n==="own"?h:n==="instance"?this:void 0}let o=`The method "${i}" could not be called on the instance of TypingEffect - the current state "${this.#t}" does not match the allowed state${Array.isArray(e)?"s":""} "${l}".`;throw this.#t==="initialized"&&(o+=' Provide the "strings" and "callback" arguments.'),new Error(o)};#N=()=>{this.#p(u(this.#o,{rewindStringOnFinish:this.#e.untypeString})),this.#t="running",this.#r="cycleStart",this.#d=null,this.#w=!1,this.#i=null,this.#s=null};#B=()=>{this.#d||(this.#d=this.#r,this.#r="idle",this.#i=null,this.#s=null)};#U=()=>{this.#d&&(this.#r=this.#d,this.#d=null,this.#i=null,this.#s=null)};#M=()=>{this.#r="idle",this.#t=this.#c("ready"),this.#d=null,this.#i=null,this.#s=null};#j=()=>{this.#R&&cancelAnimationFrame(this.#R.rafId),this.#R=null,this.#t="disposed",this.#r="idle",this.#o=[],this.#y=null,this.#n.iterator=null,this.#R=null,this.#f=this.#O(),this.#v=null,this.#i=null,this.#s=null};#V=(t=this.#a.index,i)=>{let e=()=>{this.#p(u(this.#o,{rewindStringOnFinish:this.#e.untypeString,startAtString:t}))},n=()=>{e(),this.#t==="running"?(this.#s=null,this.#i=null):(this.#s="idle",this.#i=null)};i?(()=>{e();let s=this.#m("cycleStart");this.#r=s,this.#i=null,this.#s=null})():this.#u("cycleStart",n,!0)};#c=(t=this.#t)=>this.#n.iterator!==null&&typeof this.#y=="function"?this.#t==="initialized"?"ready":t:"initialized";#m=(t=this.#r)=>this.#t==="running"?t:"idle";constructor(t,i,e){t&&this.#A(t),i&&this.#C(i),this.#E(e),this.#r="idle",this.#v={...this.#h("idle","idle",n=>{this.#e.showCursor&&this.#T(this.#I(n))}),...this.#h("cycleStart","delayBeforeTyping",()=>(this.#x(this.#n.iterator?.next()||this.#n.lastResult),this.#w=!0,"done")),...this.#h("delayBeforeTyping","beforeTyping",n=>{if(n>=this.#g+this.#e.delayBeforeTyping)return"done";this.#e.showCursor&&this.#T(this.#I(n,""))}),...this.#h("beforeTyping","typing"),...this.#h("typing","afterTyping",n=>{let r=Math.max(this.#g,this.#a.changeTimestamp)+this.#e.typingDelay;if(n>=r){let s=Math.floor(Math.random()*this.#e.typingVariation);if(n>=r+s&&(this.#a.changeTimestamp=n,this.#w||this.#x(this.#n.iterator?.next()||this.#n.lastResult),this.#w=!1,this.#n.lastResult.value)){if("string"in this.#n.lastResult.value)this.#T(this.#n.lastResult.value.string+(this.#e.showCursor?this.#e.cursorSymbol.typing:""));else if("stringEnd"in this.#n.lastResult.value&&this.#n.lastResult.value.stringEnd)return"done"}}}),...this.#h("afterTyping","delayAfterTyping"),...this.#h("delayAfterTyping",()=>this.#e.untypeString?"beforeUntyping":"cycleStart",n=>{if(n>=this.#g+this.#e.delayAfterTyping)return"done";this.#e.showCursor&&this.#T(this.#I(n))}),...this.#h("beforeUntyping","untyping"),...this.#h("untyping","afterUntyping",n=>{if(n>=Math.max(this.#g,this.#a.changeTimestamp)+this.#e.untypingDelay&&(this.#a.changeTimestamp=n,this.#x(this.#n.iterator?.next()||this.#n.lastResult),this.#n.lastResult.value)){if("string"in this.#n.lastResult.value)this.#T(this.#n.lastResult.value.string+(this.#e.showCursor?this.#e.cursorSymbol.untyping:""));else if("stringRewindEnd"in this.#n.lastResult.value&&this.#n.lastResult.value.stringRewindEnd)return"done"}}),...this.#h("afterUntyping","cycleStart")}}start=this.#l(this.#N,"start",["running","ready"],"instance");pause=this.#l(this.#B,"pause","running","instance");resume=this.#l(this.#U,"resume","running","instance");stop=this.#l(this.#M,"stop","any","instance");jumpTo=this.#l(this.#V,"jumpTo","running","instance");dispose=this.#l(this.#j,"dispose","any","void");setStrings=this.#l(this.#A,"setStrings","any","instance");setCallback=this.#l(this.#C,"setCallback","any","instance");setOptions=this.#l(this.#E,"setOptions","any","instance");onBeforeTyping=this.#l((t,i=!1)=>{if(typeof t!="function")throw new Error("Provided callback is not a function.");let e=this.#u("beforeTyping",()=>{t(this.#a.index)},i);return()=>this.#b("beforeTyping",e)},"onBeforeTyping","any","own");onAfterTyping=this.#l((t,i=!1)=>{if(typeof t!="function")throw new Error("Provided callback is not a function.");let e=this.#u("afterTyping",()=>{t(this.#a.index)},i);return()=>this.#b("afterTyping",e)},"onAfterTyping","any","own");onBeforeUntyping=this.#l((t,i=!1)=>{if(typeof t!="function")throw new Error("Provided callback is not a function.");let e=this.#u("beforeUntyping",()=>{t(this.#a.index)},i);return()=>this.#b("beforeUntyping",e)},"onBeforeUntyping","any","own");onAfterUntyping=this.#l((t,i=!1)=>{if(typeof t!="function")throw new Error("Provided callback is not a function.");let e=this.#u("afterUntyping",()=>{t(this.#a.index)},i);return()=>this.#b("afterUntyping",e)},"onAfterUntyping","any","own");onArrayFinished=this.#l((t,i=!1)=>{if(typeof t!="function")throw new Error("Provided callback is not a function.");let e=this.#u("arrayFinished",t,i);return()=>this.#b("arrayFinished",e)},"onArrayFinished","any","own")};export{g as TypingEffect}; | ||
function*d(a,t){if(t){let i=a.length;for(;i>=0;)yield a.substring(0,i),i--}else{let i=1;for(;a.length>=i;)yield a.substring(0,i),i++}}function*u(a,t){let i=t?.rewindStringOnFinish,e=t?.startAtString??0,n=e<a.length&&e>0?e:0;for(;a.length>n;){let r=a[n]||"",s=d(r),o=s.next();for(;!o.done;)yield{string:o.value,index:n},o=s.next();if(yield{stringEnd:!0},i){let l=d(r,!0),h=l.next();for(;!h.done;)yield{string:h.value,index:n},h=l.next();yield{stringRewindEnd:!0}}n++}}function g(a,t=0){let i=performance.now(),e={rafId:0};function n(r){let s=r-i;s=s>=0?s:0,s>t&&(i=r,a(r)),e.rafId=requestAnimationFrame(n)}return e.rafId=requestAnimationFrame(n),e}var c=(a,t,i)=>{if(JSON.stringify(t,(e,n)=>n===void 0?"und":n)==="{}")return a;{let e={};for(let n in a)if(a.hasOwnProperty(n))if(i.hasOwnProperty(n))if(t.hasOwnProperty(n)){let r=a[n],s=t[n],o=i[n];if(s===void 0)e[n]=o;else switch(!0){case(Array.isArray(r)&&Array.isArray(s)):{e[n]=structuredClone(s);break}case(typeof r=="object"&&r!==null&&typeof s=="object"&&s!==null):{e[n]=c(r,s,o);break}default:{e[n]=s;break}}}else e[n]=a[n];else throw new Error(`Structure mismatch error. The "default object" does not have property "${n}" of the "current object".`);return e}};var p=()=>({typingDelay:100,untypingDelay:30,delayBeforeTyping:1600,delayAfterTyping:3e3,untypeString:!0,typingVariation:100,showCursor:!0,cursorSymbol:{typing:"|",untyping:"|",blinking:"|"},cursorBlinkRate:500,loop:!0}),y=class{#e=p();#A=()=>({...this.#e,cursorSymbol:{...this.#e.cursorSymbol}});get options(){return this.#A()}#n={iterator:null,initialResult:{done:!1,value:{string:"",index:0}},lastResult:{done:!1,value:{string:"",index:0}}};#p=t=>{this.#n.iterator=t,this.#n.lastResult=this.#n.initialResult};#x=t=>{this.#n.lastResult=t,t.value&&"string"in t.value&&(this.#a.value=t.value.string,this.#a.index=t.value.index,this.#a.nextExpectedIndex=t.value.index+1<this.#l.length?t.value.index+1:0)};#l=[];get strings(){return[...this.#l]}#E=(t,i)=>{if(!Array.isArray(t))throw new Error("Provided strings is not an array.");let e=()=>{this.#l=t,this.#l.length>0?this.#p(u(this.#l,{rewindStringOnFinish:this.#e.untypeString})):this.#p(null)},n=()=>{e(),this.#t=this.#g(),this.#i=null,this.#s=null},r=()=>{e(),this.#t=this.#g();let l=this.#S();this.#t!=="initialized"?(this.#t="running",this.#i=null,this.#s=null):(this.#i=null,this.#s=l)},s=()=>{e(),this.#t=this.#g();let l=this.#S("cycleStart");this.#t==="running"?(this.#i=l,this.#s=null):(this.#i=null,this.#s=null,this.#r=l)},o=()=>{i?s():this.#h("cycleStart",r,!0)};this.instanceState==="running"?o():n()};#d=null;get callback(){return this.#d}#C=(t,i)=>{if(t!==null&&typeof t!="function")throw new Error("Provided callback is not a function or null.");let e=()=>{this.#d=t,this.#t=this.#g(),this.#i=null,this.#s=null},n=()=>{this.#d=t,this.#t=this.#g();let o=this.#S();this.#t!=="initialized"?(this.#t="running",this.#i=null,this.#s=null):(this.#i=null,this.#s=o)},r=()=>{this.#d=t,this.#t=this.#g();let o=this.#S();this.#t==="running"?(this.#i=null,this.#s=null):(this.#i=null,this.#s=null,this.#r=o)},s=()=>{i?r():this.#h("cycleStart",n,!0)};this.instanceState==="running"?s():e()};#P=t=>{let i=this.#A(),e=p();if(t){let{cursorSymbol:n,...r}=t,s=r;if(t.hasOwnProperty("cursorSymbol"))switch(!0){case n===void 0:s.cursorSymbol={typing:void 0,untyping:void 0,blinking:void 0};break;case typeof n=="object":s.cursorSymbol=n;break;default:s.cursorSymbol={typing:n,untyping:n,blinking:n};break}return c(i,s,e)}else return i};#O=(t,i)=>{if(t!==void 0&&(typeof t!="object"||t===null))throw new Error("Provided options is not an object.");let e=()=>{let l=this.#P(t),h=!1;return this.#e.untypeString!==l.untypeString&&(this.#p(u(this.#l,{rewindStringOnFinish:l.untypeString})),h=!0),this.#e=l,h},n=()=>{e()},r=()=>{e()},s=()=>{let l=e(),h=this.#S("cycleStart");l&&(this.#r=h,this.#i=null,this.#s=null)},o=()=>{i?s():this.#h("cycleStart",r,!0)};this.instanceState==="running"?o():n()};#b={visible:!1,lastChangeTimestamp:0};#R=g(t=>{this.#t==="running"&&this.#v&&(!this.#n.lastResult.done||this.#r==="idle"?(this.#i&&(this.#r=this.#i,this.#i=null),this.#r=this.#v[this.#r].handle(t),this.#s&&(this.#r=this.#s,this.#s=null)):(this.#I("arrayFinished"),this.#e.loop?(this.#p(u(this.#l,{rewindStringOnFinish:this.#e.untypeString})),this.#r="cycleStart"):this.#r="idle"))});#a={value:"",index:0,nextExpectedIndex:0,changeTimestamp:0,callbackString:""};#t="initialized";get instanceState(){return this.#t}#r;get runningState(){return this.#r}#i=null;#s=null;#c=null;#v=null;#w=!1;#F=()=>({cycleStart:[],idle:[],beforeTyping:[],typing:[],afterTyping:[],delayAfterTyping:[],beforeUntyping:[],untyping:[],afterUntyping:[],delayBeforeTyping:[],arrayFinished:[],instanceDisposed:[]});#f=this.#F();#h=(t,i,e)=>{let n=Symbol("cbId");return this.#f[t].push({callback:i,once:e,called:!1,id:n,remove:!1}),n};#m=(t,i)=>{let e=this.#f[t].find(({id:n})=>n===i);e&&(e.remove=!0)};#I=t=>{if(this.#t!=="disposed"){let i=[];this.#f[t].forEach((e,n)=>{(e.once||e.remove)&&i.unshift(n),!e.called&&!e.remove&&(e.callback(),e.called=!0)}),i.forEach(e=>{this.#f[t].splice(e,1)}),this.#f[t].forEach(e=>{e.called=!1})}};#y=0;#u=(t,i,e)=>({[t]:{handle:n=>{this.#I(t);let r=typeof i=="function"?i():i;return e?e(n)==="done"?(this.#y=n,r):t:(this.#y=n,r)}}});#T=t=>{typeof this.#d=="function"&&t!==this.#a.callbackString&&(this.#d(t),this.#a.callbackString=t)};#k(t,i=this.#a.value){return t>=Math.max(this.#a.changeTimestamp,this.#y,this.#b.lastChangeTimestamp)+this.#e.cursorBlinkRate&&(this.#b.lastChangeTimestamp=t,this.#b.visible=!this.#b.visible),i+(this.#b.visible?this.#e.cursorSymbol.blinking:"")}#o=(t,i,e,n)=>(...r)=>{let s=()=>e==="any"?!0:Array.isArray(e)?e.includes(this.#t):this.#t===e;if(this.#t==="disposed")throw new Error(`The method "${i}" could not be called - the instance of TypingEffect was disposed. Create a new one.`);let o=Array.isArray(e)?e.join(", "):e;if(s()){let h=t.apply(this,r);return n==="own"?h:n==="instance"?this:void 0}let l=`The method "${i}" could not be called on the instance of TypingEffect - the current state "${this.#t}" does not match the allowed state${Array.isArray(e)?"s":""} "${o}".`;throw this.#t==="initialized"&&(l+=' Provide the "strings" and "callback" arguments.'),new Error(l)};#N=()=>{this.#p(u(this.#l,{rewindStringOnFinish:this.#e.untypeString})),this.#t="running",this.#r="cycleStart",this.#c=null,this.#w=!1,this.#i=null,this.#s=null};#D=()=>{this.#c||(this.#c=this.#r,this.#r="idle",this.#i=null,this.#s=null)};#B=()=>{this.#c&&(this.#r=this.#c,this.#c=null,this.#i=null,this.#s=null)};#U=()=>{this.#r="idle",this.#t=this.#g("ready"),this.#c=null,this.#i=null,this.#s=null};#M=()=>{this.#R&&cancelAnimationFrame(this.#R.rafId),this.#R=null,this.#I("instanceDisposed"),this.#t="disposed",this.#r="idle",this.#l=[],this.#d=null,this.#n.iterator=null,this.#R=null,this.#f=this.#F(),this.#v=null,this.#i=null,this.#s=null};#j=(t=this.#a.index,i)=>{let e=()=>{this.#p(u(this.#l,{rewindStringOnFinish:this.#e.untypeString,startAtString:t}))},n=()=>{e(),this.#t==="running"?(this.#s=null,this.#i=null):(this.#s="idle",this.#i=null)};i?(()=>{e();let s=this.#S("cycleStart");this.#r=s,this.#i=null,this.#s=null})():this.#h("cycleStart",n,!0)};#g=(t=this.#t)=>this.#n.iterator!==null&&typeof this.#d=="function"?this.#t==="initialized"?"ready":t:"initialized";#S=(t=this.#r)=>this.#t==="running"?t:"idle";constructor(t,i,e){t&&this.#E(t),i&&this.#C(i),this.#O(e),this.#r="idle",this.#v={...this.#u("idle","idle",n=>{this.#e.showCursor&&this.#T(this.#k(n))}),...this.#u("cycleStart","delayBeforeTyping",()=>(this.#x(this.#n.iterator?.next()||this.#n.lastResult),this.#w=!0,"done")),...this.#u("delayBeforeTyping","beforeTyping",n=>{if(n>=this.#y+this.#e.delayBeforeTyping)return"done";this.#e.showCursor&&this.#T(this.#k(n,""))}),...this.#u("beforeTyping","typing"),...this.#u("typing","afterTyping",n=>{let r=Math.max(this.#y,this.#a.changeTimestamp)+this.#e.typingDelay;if(n>=r){let s=Math.floor(Math.random()*this.#e.typingVariation);if(n>=r+s&&(this.#a.changeTimestamp=n,this.#w||this.#x(this.#n.iterator?.next()||this.#n.lastResult),this.#w=!1,this.#n.lastResult.value)){if("string"in this.#n.lastResult.value)this.#T(this.#n.lastResult.value.string+(this.#e.showCursor?this.#e.cursorSymbol.typing:""));else if("stringEnd"in this.#n.lastResult.value&&this.#n.lastResult.value.stringEnd)return"done"}}}),...this.#u("afterTyping","delayAfterTyping"),...this.#u("delayAfterTyping",()=>this.#e.untypeString?"beforeUntyping":"cycleStart",n=>{if(n>=this.#y+this.#e.delayAfterTyping)return"done";this.#e.showCursor&&this.#T(this.#k(n))}),...this.#u("beforeUntyping","untyping"),...this.#u("untyping","afterUntyping",n=>{if(n>=Math.max(this.#y,this.#a.changeTimestamp)+this.#e.untypingDelay&&(this.#a.changeTimestamp=n,this.#x(this.#n.iterator?.next()||this.#n.lastResult),this.#n.lastResult.value)){if("string"in this.#n.lastResult.value)this.#T(this.#n.lastResult.value.string+(this.#e.showCursor?this.#e.cursorSymbol.untyping:""));else if("stringRewindEnd"in this.#n.lastResult.value&&this.#n.lastResult.value.stringRewindEnd)return"done"}}),...this.#u("afterUntyping","cycleStart")}}start=this.#o(this.#N,"start",["running","ready"],"instance");pause=this.#o(this.#D,"pause","running","instance");resume=this.#o(this.#B,"resume","running","instance");stop=this.#o(this.#U,"stop","any","instance");jumpTo=this.#o(this.#j,"jumpTo","running","instance");dispose=this.#o(this.#M,"dispose","any","void");setStrings=this.#o(this.#E,"setStrings","any","instance");setCallback=this.#o(this.#C,"setCallback","any","instance");setOptions=this.#o(this.#O,"setOptions","any","instance");onBeforeTyping=this.#o((t,i=!1)=>{if(typeof t!="function")throw new Error("Provided callback is not a function.");let e=this.#h("beforeTyping",()=>{t(this.#a.index)},i);return()=>this.#m("beforeTyping",e)},"onBeforeTyping","any","own");onAfterTyping=this.#o((t,i=!1)=>{if(typeof t!="function")throw new Error("Provided callback is not a function.");let e=this.#h("afterTyping",()=>{t(this.#a.index)},i);return()=>this.#m("afterTyping",e)},"onAfterTyping","any","own");onBeforeUntyping=this.#o((t,i=!1)=>{if(typeof t!="function")throw new Error("Provided callback is not a function.");let e=this.#h("beforeUntyping",()=>{t(this.#a.index)},i);return()=>this.#m("beforeUntyping",e)},"onBeforeUntyping","any","own");onAfterUntyping=this.#o((t,i=!1)=>{if(typeof t!="function")throw new Error("Provided callback is not a function.");let e=this.#h("afterUntyping",()=>{t(this.#a.index)},i);return()=>this.#m("afterUntyping",e)},"onAfterUntyping","any","own");onArrayFinished=this.#o((t,i=!1)=>{if(typeof t!="function")throw new Error("Provided callback is not a function.");let e=this.#h("arrayFinished",t,i);return()=>this.#m("arrayFinished",e)},"onArrayFinished","any","own");onInstanceDisposed=this.#o(t=>{if(typeof t!="function")throw new Error("Provided callback is not a function.");let i=this.#h("instanceDisposed",t,!0);return()=>this.#m("instanceDisposed",i)},"onInstanceDisposed","any","own")};export{y as TypingEffect}; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "typing-effect-ts", | ||
"version": "1.3.5", | ||
"version": "1.4.0", | ||
"description": "A small TypeScript package that provides the ability to create a typing effect with one or multiple strings.", | ||
@@ -37,9 +37,9 @@ "repository": { | ||
"devDependencies": { | ||
"@vitest/coverage-v8": "^1.5.0", | ||
"jsdom": "^24.0.0", | ||
"@vitest/coverage-v8": "^2.0.0", | ||
"jsdom": "^25.0.1", | ||
"tsup": "^8.0.2", | ||
"typescript": "^5.4.5", | ||
"vite": "^5.2.10", | ||
"vitest": "^1.3.1" | ||
"vitest": "^2.0.0" | ||
} | ||
} |
297
README.md
# Typing Effect | ||
| Source | Tests | Coverage | | ||
|--------|-------|----------| | ||
| [![main branch](https://img.shields.io/github/package-json/v/ydernov/typing-effect/main?logo=github&label=main)](https://github.com/ydernov/typing-effect/tree/main) | [![Tests main](https://github.com/ydernov/typing-effect/actions/workflows/test-on-push.yml/badge.svg?branch=main)](https://github.com/ydernov/typing-effect/actions/workflows/test-on-push.yml?query=branch%3Amain) | [![Coverage main](https://img.shields.io/endpoint?url=https%3A%2F%2Fydernov.github.io%2Ftyping-effect%2Fcoverage%2Fmain%2Fbadge.json)](https://ydernov.github.io/typing-effect/coverage/main/index.html) | | ||
| [![Release](https://img.shields.io/github/v/release/ydernov/typing-effect?label=Release&logo=github)](https://github.com/ydernov/typing-effect/releases/latest) | | [![Coverage release](https://img.shields.io/endpoint?url=https%3A%2F%2Fydernov.github.io%2Ftyping-effect%2Fcoverage%2Frelease%2Fbadge.json)](https://ydernov.github.io/typing-effect/coverage/release/index.html) | | ||
| Source | Tests | Coverage | | ||
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| [![main branch](https://img.shields.io/github/package-json/v/ydernov/typing-effect/main?logo=github&label=main)](https://github.com/ydernov/typing-effect/tree/main) | [![Tests main](https://github.com/ydernov/typing-effect/actions/workflows/test-on-push.yml/badge.svg?branch=main)](https://github.com/ydernov/typing-effect/actions/workflows/test-on-push.yml?query=branch%3Amain) | [![Coverage main](https://img.shields.io/endpoint?url=https%3A%2F%2Fydernov.github.io%2Ftyping-effect%2Fcoverage%2Fmain%2Fbadge.json)](https://ydernov.github.io/typing-effect/coverage/main/index.html) | | ||
| [![Release](https://img.shields.io/github/v/release/ydernov/typing-effect?label=Release&logo=github)](https://github.com/ydernov/typing-effect/releases/latest) | | [![Coverage release](https://img.shields.io/endpoint?url=https%3A%2F%2Fydernov.github.io%2Ftyping-effect%2Fcoverage%2Frelease%2Fbadge.json)](https://ydernov.github.io/typing-effect/coverage/release/index.html) | | ||
## Description | ||
@@ -16,3 +12,3 @@ | ||
Check the [Usage notes](#usage-notes) section and [demo](https://ydernov.github.io/typing-effect/) for more information and examples. | ||
Check the [Usage notes](#usage-notes) section and [demo](https://ydernov.github.io/typing-effect/) for more information and examples. | ||
Or take a look at [dev notes](https://github.com/ydernov/typing-effect/blob/main/DEVNOTES.md). | ||
@@ -27,7 +23,11 @@ | ||
### Or via script tag | ||
Thanks to [JSDELIVR](https://www.jsdelivr.com) | ||
Thanks to services like [JSDELIVR](https://www.jsdelivr.com) and [UNPKG](https://www.unpkg.com) :blush:. | ||
```html | ||
<script type="module"> | ||
import { TypingEffect } from "https://cdn.jsdelivr.net/npm/typing-effect-ts/dist/index.js"; | ||
<!-- OR --> | ||
import { TypingEffect } from "https://unpkg.com/typing-effect-ts/dist/index.js"; | ||
const te = new TypingEffect(); | ||
@@ -45,3 +45,3 @@ </script> | ||
Provide the instance with string array and a callback function where we set the contents of our div: | ||
Provide the instance with string array and a callback function where we set the contents of our div: | ||
@@ -62,3 +62,2 @@ ```js | ||
); | ||
``` | ||
@@ -76,2 +75,120 @@ | ||
### React Example | ||
1. [About the custom hook](#Creating-a-custom-hook) | ||
1. [Version 1.4.0 example](#TypingEffect-v140-example) | ||
1. [Prior to version 1.4.0 example](#TypingEffect-prior-to-v140-example) | ||
1. [Usage in a component](#Usage-in-a-component) | ||
#### Creating a custom hook | ||
To use TypingEffect with React I suggest creating a custom hook. This will make it easy to use by abstracting the cleanup fuctionality and returning a reactive instance reference via `useState` hook. | ||
##### TypingEffect v1.4.0 example: | ||
```ts | ||
import { useEffect, useState } from "react"; | ||
import { TypingEffect } from "typing-effect-ts"; | ||
export const useTypingEffect = () => { | ||
const [te, setTe] = useState<TypingEffect | null>(null); | ||
useEffect(() => { | ||
te?.onInstanceDisposed(() => { | ||
setTe(null); | ||
}); | ||
}, [te]); | ||
useEffect(() => { | ||
if (!te) { | ||
setTe(new TypingEffect()); | ||
} else { | ||
return () => { | ||
if (te.instanceState !== "disposed") { | ||
te.dispose(); | ||
} | ||
}; | ||
} | ||
}, [te]); | ||
return te; | ||
}; | ||
``` | ||
##### TypingEffect prior to v1.4.0 example: | ||
```ts | ||
import { useCallback, useEffect, useMemo, useState } from "react"; | ||
import { TypingEffect } from "typing-effect-ts"; | ||
export const useTypingEffect = () => { | ||
const [te, setTe] = useState<TypingEffect | null>(null); | ||
const originalDispose = useMemo(() => te?.dispose, [te]); | ||
const dispose = useCallback(() => { | ||
originalDispose?.(); | ||
setTe(null); | ||
}, [originalDispose]); | ||
useEffect(() => { | ||
if (!te) { | ||
setTe(new TypingEffect()); | ||
} else { | ||
te.dispose = dispose; | ||
return () => { | ||
if (te.instanceState !== "disposed") { | ||
te.dispose(); | ||
} | ||
}; | ||
} | ||
}, [te, dispose]); | ||
return te; | ||
}; | ||
``` | ||
This example shows how to change the `dispose` method on the instance with a custom wrapper function, because in JS you can actually do that. | ||
Another, arguably "cleaner," solution would be extending the TypingEffect class, modifying `dispose`, and adding your custom functionality. More info in [this article](https://javascript.info/class-inheritance#overriding-a-method). | ||
> Note: While no longer necessary (since v1.4.0), this approach may still be useful when working with other third-party packages. | ||
#### Usage in a component | ||
Call the hook: | ||
```ts | ||
const te = useTypingEffect(); | ||
``` | ||
Have a typing element: | ||
```tsx | ||
<p> | ||
Typing: <span ref={typingRef}></span> | ||
</p> | ||
``` | ||
Write your logic inside ref/useCallback: | ||
```ts | ||
const typingRef = useCallback( | ||
(ref: HTMLSpanElement) => { | ||
if (ref && te) { | ||
te.setStrings(["one", "two", "three"]) | ||
.setCallback((st) => { | ||
ref.innerText = st; | ||
}) | ||
.start(); | ||
} | ||
}, | ||
[te] | ||
); | ||
``` | ||
> Note: Notice the check for `ref`. It's necessary because `ref` becomes `null` during component unmounting. Which may happen independently from `te` value change. | ||
Why set contents with `ref.innerText` instead of `setState`? Because the data received from the typing callback is not meant to be part of the business logic. While there may be cases where you need to use it this way, it is intended primarily for presentation and filler content purposes. | ||
Otherwise, I trust you know what you're doing. 😉 | ||
## API | ||
@@ -83,5 +200,6 @@ | ||
Starts the iteration over strings. Requires strings and callback to be set. If called while running, restarts iteration from the first string. | ||
Starts the iteration over strings. Requires strings and callback to be set. If called while running, restarts iteration from the first string. | ||
Usage: | ||
```js | ||
@@ -96,2 +214,3 @@ te.start(); | ||
Usage: | ||
```js | ||
@@ -106,5 +225,7 @@ te.stop(); | ||
Usage: | ||
```js | ||
te.pause(); | ||
``` | ||
Calling the pause in the middle of typing: | ||
@@ -119,2 +240,3 @@ | ||
Usage: | ||
```js | ||
@@ -129,2 +251,3 @@ te.resume(); | ||
Syntax: | ||
```ts | ||
@@ -135,6 +258,8 @@ jumpTo: (stringIndex?: number, now?: boolean) => this; | ||
Has two possible arguments: | ||
* `stringIndex`: Optional number. The index of the string to jump to. Defaults to the current string index. | ||
* `now`: Optional boolean. Indicates whether to execute the jump immediately. Defaults to false. | ||
- `stringIndex`: Optional number. The index of the string to jump to. Defaults to the current string index. | ||
- `now`: Optional boolean. Indicates whether to execute the jump immediately. Defaults to false. | ||
Usage: | ||
```js | ||
@@ -152,2 +277,3 @@ te.jumpTo(); // will restart the iteration of the current string after it finishes | ||
Syntax: | ||
```ts | ||
@@ -158,6 +284,8 @@ setStrings: (strings: string[], now?: boolean) => this; | ||
Has two possible arguments: | ||
* `strings`: An array of new strings. | ||
* `now`: Optional boolean. Indicates whether to set new strings immediately. Defaults to false. | ||
- `strings`: An array of new strings. | ||
- `now`: Optional boolean. Indicates whether to set new strings immediately. Defaults to false. | ||
Usage: | ||
```js | ||
@@ -176,11 +304,15 @@ // will set new strings and start the iteration from "My new favourite string" after the current string cycle finishes | ||
Syntax: | ||
```ts | ||
setCallback: (callback: ((string: string) => void) | null, now?: boolean) => this; | ||
setCallback: (callback: ((string: string) => void) | null, now?: boolean) => | ||
this; | ||
``` | ||
Has two possible arguments: | ||
* `callback`: A function which accepts a string argument. | ||
* `now`: Optional boolean. Indicates whether to set new callback immediately. Defaults to false. | ||
- `callback`: A function which accepts a string argument. | ||
- `now`: Optional boolean. Indicates whether to set new callback immediately. Defaults to false. | ||
Usage: | ||
```js | ||
@@ -200,3 +332,2 @@ // will set new callback after the current string cycle finishes | ||
### setOptions | ||
@@ -209,2 +340,3 @@ | ||
Syntax: | ||
```ts | ||
@@ -215,6 +347,8 @@ setOptions: (options?: TypingEffectOptions, now?: boolean) => this; | ||
Has two possible arguments: | ||
* `options`: Options object. [About options](#Options). | ||
* `now`: Optional boolean. Indicates whether to update options immediately. Defaults to false. | ||
- `options`: Options object. [About options](#Options). | ||
- `now`: Optional boolean. Indicates whether to update options immediately. Defaults to false. | ||
Usage: | ||
```js | ||
@@ -238,2 +372,3 @@ // will update provided options after the current string cycle finishes | ||
Syntax: | ||
```ts | ||
@@ -244,6 +379,8 @@ onBeforeTyping: (callback: (stringIndex: number) => void, once?: boolean) => () => void; | ||
Has two possible arguments: | ||
* `callback`: A function that will be called with the current string index as its argument. | ||
* `once`: Optional boolean. Indicates whether the callback should be executed only once. Defaults to false. | ||
- `callback`: A function that will be called with the current string index as its argument. | ||
- `once`: Optional boolean. Indicates whether the callback should be executed only once. Defaults to false. | ||
Usage: | ||
```js | ||
@@ -269,2 +406,3 @@ // logs the index once before typing | ||
Syntax: | ||
```ts | ||
@@ -275,6 +413,8 @@ onAfterTyping: (callback: (stringIndex: number) => void, once?: boolean) => () => void; | ||
Has two possible arguments: | ||
* `callback`: A function that will be called with the current string index as its argument. | ||
* `once`: Optional boolean. Indicates whether the callback should be executed only once. Defaults to false. | ||
- `callback`: A function that will be called with the current string index as its argument. | ||
- `once`: Optional boolean. Indicates whether the callback should be executed only once. Defaults to false. | ||
Usage: | ||
```js | ||
@@ -301,2 +441,3 @@ // logs the index once after typing | ||
Syntax: | ||
```ts | ||
@@ -307,6 +448,8 @@ onBeforeUntyping: (callback: (stringIndex: number) => void, once?: boolean) => () => void; | ||
Has two possible arguments: | ||
* `callback`: A function that will be called with the current string index as its argument. | ||
* `once`: Optional boolean. Indicates whether the callback should be executed only once. Defaults to false. | ||
- `callback`: A function that will be called with the current string index as its argument. | ||
- `once`: Optional boolean. Indicates whether the callback should be executed only once. Defaults to false. | ||
Usage: | ||
```js | ||
@@ -333,2 +476,3 @@ // logs the index once before untyping | ||
Syntax: | ||
```ts | ||
@@ -339,6 +483,8 @@ onAfterUntyping: (callback: (stringIndex: number) => void, once?: boolean) => () => void; | ||
Has two possible arguments: | ||
* `callback`: A function that will be called with the current string index as its argument. | ||
* `once`: Optional boolean. Indicates whether the callback should be executed only once. Defaults to false. | ||
- `callback`: A function that will be called with the current string index as its argument. | ||
- `once`: Optional boolean. Indicates whether the callback should be executed only once. Defaults to false. | ||
Usage: | ||
```js | ||
@@ -364,2 +510,3 @@ // logs the index once after untyping | ||
Syntax: | ||
```ts | ||
@@ -370,6 +517,8 @@ onArrayFinished: (callback: () => void, once?: boolean) => () => void; | ||
Has two possible arguments: | ||
* `callback`: A function to be called when array finishes. | ||
* `once`: Optional boolean. Indicates whether the callback should be executed only once. Defaults to false. | ||
- `callback`: A function to be called when array finishes. | ||
- `once`: Optional boolean. Indicates whether the callback should be executed only once. Defaults to false. | ||
Usage: | ||
```js | ||
@@ -390,12 +539,43 @@ // logs the message once | ||
### onInstanceDisposed | ||
Available since `v1.4.0`. | ||
Registers a callback that will be called when instance is being disposed right before setting `instanceState` to `disposed`. Returns a function that removes the callback. | ||
Syntax: | ||
```ts | ||
onInstanceDisposed: (callback: () => void) => () => void; | ||
``` | ||
Has only one argument, because it can only be called once: | ||
- `callback`: A function to be called when disposing. | ||
Usage: | ||
```js | ||
// logs the message and does some cleanup | ||
const removeCleanup = te.onInstanceDisposed(() => { | ||
console.log("The instance is disposed!"); | ||
// performe some kind of cleanup or other operations | ||
// ... | ||
// ... | ||
}); | ||
// removes the above callback | ||
removeCleanup(); | ||
``` | ||
### dispose | ||
Disposes of the instance, resetting its state and helping to "release" resources. It cancels any ongoing animation frames, resets the running state, and clears all internal data structures. This method should be called when the instance is no longer needed. | ||
Disposes of the instance, resetting its state and helping to "release" resources. It cancels any ongoing animation frames, resets the running state, and clears all internal data structures. This method should be called when the instance is no longer needed. | ||
While not mandatory, it may be useful in some cases. | ||
After calling `dispose`, all subsequent method calls will throw errors. | ||
While not mandatory, it may be useful in some cases. Such as in SPAs in conjunction with [`onInstanceDisposed`](#onInstanceDisposed) method (v1.4.0). See [React usage example](#react-example). After calling `dispose`, all subsequent method calls will throw errors. | ||
Usage: | ||
```js | ||
te.dispose() | ||
te.dispose(); | ||
``` | ||
@@ -406,2 +586,3 @@ | ||
The options object has the following structure: | ||
```ts | ||
@@ -422,5 +603,5 @@ type TypingEffectOptions = { | ||
type CursorSymbols = { | ||
typing: string; | ||
untyping: string; | ||
blinking: string; | ||
typing: string; | ||
untyping: string; | ||
blinking: string; | ||
}; | ||
@@ -430,2 +611,3 @@ ``` | ||
### typingDelay | ||
`number` | ||
@@ -435,4 +617,4 @@ | ||
### untypingDelay | ||
### untypingDelay | ||
`number` | ||
@@ -442,4 +624,4 @@ | ||
### delayBeforeTyping | ||
`number` | ||
@@ -449,9 +631,9 @@ | ||
### delayAfterTyping | ||
### delayAfterTyping | ||
`number` | ||
Delay after a string is typed, in milliseconds. Defaults to `3000`ms. During this time, if `showCursor` setting is `true`, the callback function will be called with blinking cursor symbol at the rate of `cursorBlinkRate`. | ||
### untypeString | ||
### untypeString | ||
`boolean` | ||
@@ -462,4 +644,4 @@ | ||
### typingVariation | ||
### typingVariation | ||
`number` | ||
@@ -470,4 +652,4 @@ | ||
### showCursor | ||
### showCursor | ||
`boolean` | ||
@@ -478,2 +660,3 @@ | ||
### cursorSymbol | ||
`string` | `Partial<CursorSymbols>` | ||
@@ -485,2 +668,3 @@ | ||
Usage: | ||
```js | ||
@@ -493,4 +677,4 @@ // will set the cursor to "_" wile typing, untyping and blinking | ||
cursorSymbol: { | ||
untyping: "<" | ||
} | ||
untyping: "<", | ||
}, | ||
}); | ||
@@ -502,9 +686,9 @@ | ||
cursorSymbol: { | ||
untyping: undefined | ||
} | ||
}); | ||
untyping: undefined, | ||
}, | ||
}); | ||
``` | ||
### cursorBlinkRate | ||
### cursorBlinkRate | ||
`number` | ||
@@ -515,2 +699,3 @@ | ||
### loop | ||
`boolean` | ||
@@ -520,13 +705,15 @@ | ||
## Usage notes | ||
## Usage notes | ||
### Timing | ||
Don't expect exact timing in milliseconds. TypingEffect uses requestAnimationFrame, which usually calls its callback around every 16ms (sometimes longer if the website is busy (usually with JS)). This means the shortest reaction time is at least 16ms, so any timing you set will be rounded to the nearest bigger multiple of 16. | ||
Don't expect exact timing in milliseconds. TypingEffect uses requestAnimationFrame, which usually calls its callback around every 16ms (sometimes longer if the website is busy (usually with JS)). This means the shortest reaction time is at least 16ms, so any timing you set will be rounded to the nearest bigger multiple of 16. | ||
For instance, if you set `cursorBlinkRate` to 500ms, the cursor will actually blink every 512ms because 500 isn't divisible by 16, but 512 is. | ||
### Layout shift | ||
Important! If you are using TypingEffect as in the examples to set the content of some element, it's essential to remember the layout shift. See the [demo](https://ydernov.github.io/typing-effect#problem-layout-shift) for context. | ||
Important! If you are using TypingEffect as in the examples to set the content of some element, it's essential to remember the layout shift. See the [demo](https://ydernov.github.io/typing-effect#problem-layout-shift) for context. | ||
It is bad UX practice since it causes content jitter, making it harder for people to process; therefore, it should be avoided. The simplest way is to check the maximum height of the container during the cycle and set it's `height` or `min-height` accordingly. | ||
If this does not meet your requirements, refer to the solution in the demo for [layout shift](https://ydernov.github.io/typing-effect#problem-layout-shift) to understand how it can be dynamically resolved. |
Sorry, the diff of this file is not supported yet
108733
251
678