torph 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,4 +22,4 @@ var v=class g{element;options={};data;currentMeasures={};prevMeasures={};isIniti
22
22
  }
23
23
  }
24
24
  `,document.head.appendChild(e),g.styleEl=e}removeStyles(){g.styleEl&&(g.styleEl.remove(),g.styleEl=void 0)}blocks(e){return Array.from(e).reduce((n,i)=>i.segment===" "?[...n,{id:`space-${i.index}`,string:"\xA0"}]:n.find(h=>h.string===i.segment)?[...n,{id:`${i.segment}-${i.index}`,string:i.segment}]:[...n,{id:i.segment,string:i.segment}],[])}getUnscaledBoundingClientRect(e){let t=e.getBoundingClientRect(),i=window.getComputedStyle(e).transform,r=1,h=1,m=/matrix\(([^)]+)\)/,d=i.match(m);if(d){let l=d[1]?.split(",").map(Number);l&&l?.length>=4&&(r=l[0],h=l[3])}else{let l=i.match(/scaleX\(([^)]+)\)/),f=i.match(/scaleY\(([^)]+)\)/);l&&(r=parseFloat(l[1])),f&&(h=parseFloat(f[1]))}let c=t.width/r,p=t.height/h,a=t.x+(t.width-c)/2,u=t.y+(t.height-p)/2;return{x:a,y:u,width:c,height:p,top:u,right:a+c,bottom:u+p,left:a}}log(...e){this.options.debug&&console.log("[TextMorph]",...e)}};export{v as a};
25
- //# sourceMappingURL=chunk-43OPPES3.mjs.map
25
+ //# sourceMappingURL=chunk-4ZXQ2BQA.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/text-morph/index.ts"],"sourcesContent":["import type { TextMorphOptions } from \"./types\";\n\nexport type { TextMorphOptions } from \"./types\";\n\ntype Block = {\n id: string;\n string: string;\n};\ntype Measures = {\n [key: string]: { x: number; y: number };\n};\n\nexport class TextMorph {\n private element: HTMLElement;\n private options: Omit<TextMorphOptions, \"element\"> = {};\n\n private data: HTMLElement | string;\n\n private currentMeasures: Measures = {};\n private prevMeasures: Measures = {};\n private isInitialRender = true;\n\n static styleEl: HTMLStyleElement;\n\n constructor(options: TextMorphOptions) {\n this.options = {\n locale: \"en\",\n duration: 400,\n ease: \"cubic-bezier(0.19, 1, 0.22, 1)\",\n ...options,\n };\n\n this.element = options.element;\n this.element.setAttribute(\"torph-root\", \"\");\n this.element.style.transitionDuration = `${this.options.duration}ms`;\n this.element.style.transitionTimingFunction = this.options.ease!;\n\n if (options.debug) this.element.setAttribute(\"torph-debug\", \"\");\n\n this.data = this.element.innerHTML;\n\n this.addStyles();\n }\n\n destroy() {\n this.element.getAnimations().forEach((anim) => anim.cancel());\n this.element.removeAttribute(\"torph-root\");\n this.element.removeAttribute(\"torph-debug\");\n this.removeStyles();\n }\n\n update(value: HTMLElement | string) {\n if (value === this.data) return;\n this.data = value;\n\n if (this.data instanceof HTMLElement) {\n // TODO: handle HTMLElement case\n throw new Error(\"HTMLElement not yet supported\");\n } else {\n this.createTextGroup(this.data, this.element);\n }\n }\n\n private createTextGroup(value: string, element: HTMLElement) {\n const oldWidth = element.offsetWidth;\n const oldHeight = element.offsetHeight;\n\n const byWord = value.includes(\" \");\n const segmenter = new Intl.Segmenter(this.options.locale, {\n granularity: byWord ? \"word\" : \"grapheme\",\n });\n const iterator = segmenter.segment(value)[Symbol.iterator]();\n const blocks = this.blocks(iterator);\n\n this.prevMeasures = this.measure();\n const oldChildren = Array.from(element.children) as HTMLElement[];\n const newIds = new Set(blocks.map((b) => b.id));\n\n const exiting = oldChildren.filter(\n (child) =>\n !newIds.has(child.getAttribute(\"torph-id\") as string) &&\n !child.hasAttribute(\"torph-exiting\"),\n );\n\n const parentRect = this.getUnscaledBoundingClientRect(element);\n exiting.forEach((child) => {\n const rect = this.getUnscaledBoundingClientRect(child);\n child.setAttribute(\"torph-exiting\", \"\");\n child.style.position = \"absolute\";\n child.style.pointerEvents = \"none\";\n child.style.left = `${rect.left - parentRect.left}px`;\n child.style.top = `${rect.top - parentRect.top}px`;\n child.style.width = `${rect.width}px`;\n child.style.height = `${rect.height}px`;\n });\n\n oldChildren.forEach((child) => {\n const id = child.getAttribute(\"torph-id\") as string;\n if (newIds.has(id)) child.remove();\n });\n\n blocks.forEach((block) => {\n const span = document.createElement(\"span\");\n span.setAttribute(\"torph-item\", \"\");\n span.setAttribute(\"torph-id\", block.id);\n span.textContent = block.string;\n element.appendChild(span);\n });\n\n this.currentMeasures = this.measure();\n this.updateStyles();\n\n exiting.forEach((child) => {\n if (this.isInitialRender) {\n child.remove();\n return;\n }\n\n const id = child.getAttribute(\"torph-id\")!;\n\n const prev = this.prevMeasures[id];\n\n const siblings = Array.from(element.children) as HTMLElement[];\n const nearest = siblings.find((s) => {\n const sRect = s.getBoundingClientRect();\n const cRect = child.getBoundingClientRect();\n return Math.abs(sRect.left - cRect.left) < 40;\n });\n\n const nextPos = nearest\n ? this.currentMeasures[nearest.getAttribute(\"torph-id\")!]\n : prev;\n\n const dx = (nextPos ? nextPos.x - (prev?.x || 0) : 0) * 0.5;\n const dy = (nextPos ? nextPos.y - (prev?.y || 0) : 0) * 0.5;\n\n child.getAnimations().forEach((a) => a.cancel());\n const animation: Animation = child.animate(\n {\n transform: `translate(${dx}px, ${dy}px) scale(0.95)`,\n opacity: 0,\n offset: 1,\n },\n {\n duration: this.options.duration,\n easing: this.options.ease,\n fill: \"both\",\n },\n );\n animation.onfinish = () => child.remove();\n });\n\n if (this.isInitialRender) {\n this.isInitialRender = false;\n element.style.width = \"auto\";\n element.style.height = \"auto\";\n return;\n }\n\n if (oldWidth === 0 || oldHeight === 0) return;\n\n element.style.width = \"auto\";\n element.style.height = \"auto\";\n void element.offsetWidth; // force reflow\n\n const newWidth = element.offsetWidth;\n const newHeight = element.offsetHeight;\n\n element.style.width = `${oldWidth}px`;\n element.style.height = `${oldHeight}px`;\n void element.offsetWidth; // force reflow\n\n element.style.width = `${newWidth}px`;\n element.style.height = `${newHeight}px`;\n\n // TODO: move to `transitionend` event listener\n setTimeout(() => {\n element.style.width = \"auto\";\n element.style.height = \"auto\";\n }, this.options.duration);\n }\n\n private measure() {\n const children = Array.from(this.element.children) as HTMLElement[];\n const measures: Measures = {};\n\n children.forEach((child, index) => {\n if (child.hasAttribute(\"torph-exiting\")) return;\n const key = child.getAttribute(\"torph-id\") || `child-${index}`;\n measures[key] = {\n x: child.offsetLeft,\n y: child.offsetTop,\n };\n });\n\n return measures;\n }\n\n private updateStyles() {\n if (this.isInitialRender) return;\n\n const children = Array.from(this.element.children) as HTMLElement[];\n\n children.forEach((child, index) => {\n if (child.hasAttribute(\"torph-exiting\")) return;\n const key = child.getAttribute(\"torph-id\") || `child-${index}`;\n const prev = this.prevMeasures[key];\n const current = this.currentMeasures[key];\n\n const cx = current?.x || 0;\n const cy = current?.y || 0;\n\n const deltaX = prev ? prev?.x - cx : 0;\n const deltaY = prev ? prev?.y - cy : 0;\n const isNew = !prev;\n\n child.getAnimations().forEach((a) => a.cancel());\n child.animate(\n {\n transform: `translate(${deltaX}px, ${deltaY}px) scale(${isNew ? 0.95 : 1})`,\n opacity: isNew ? 0 : 1,\n offset: 0,\n },\n {\n duration: this.options.duration,\n easing: this.options.ease,\n delay: isNew ? this.options.duration! * 0.2 : 0,\n fill: \"both\",\n },\n );\n });\n }\n\n private addStyles() {\n if (TextMorph.styleEl) return;\n\n const style = document.createElement(\"style\");\n style.dataset.torph = \"true\";\n style.innerHTML = `\n[torph-root] {\n display: inline-flex; /* TODO: remove for multi-line support */\n position: relative;\n will-change: width, height;\n transition-property: width, height;\n}\n\n[torph-item] {\n display: inline-block;\n will-change: opacity, transform;\n transform: none;\n opacity: 1;\n}\n \n[torph-root][torph-debug] {\n outline:2px solid magenta;\n [torph-item] {\n outline:2px solid cyan;\n outline-offset: -4px;\n }\n}\n `;\n document.head.appendChild(style);\n TextMorph.styleEl = style;\n }\n\n private removeStyles() {\n if (TextMorph.styleEl) {\n TextMorph.styleEl.remove();\n TextMorph.styleEl = undefined!;\n }\n }\n\n // utils\n\n private blocks(iterator: Intl.SegmentIterator<Intl.SegmentData>) {\n const uniqueStrings: Block[] = Array.from(iterator).reduce(\n (acc, string) => {\n if (string.segment === \" \") {\n return [...acc, { id: `space-${string.index}`, string: \"\\u00A0\" }];\n }\n\n const existingString = acc.find((x) => x.string === string.segment);\n if (existingString) {\n return [\n ...acc,\n { id: `${string.segment}-${string.index}`, string: string.segment },\n ];\n }\n\n return [\n ...acc,\n {\n id: string.segment,\n string: string.segment,\n },\n ];\n },\n [] as Block[],\n );\n\n return uniqueStrings;\n }\n\n private getUnscaledBoundingClientRect(element: HTMLElement) {\n const scaledRect = element.getBoundingClientRect();\n const computedStyle = window.getComputedStyle(element);\n const transform = computedStyle.transform;\n\n let scaleX = 1;\n let scaleY = 1;\n\n const matrixRegex = /matrix\\(([^)]+)\\)/;\n const match = transform.match(matrixRegex);\n\n if (match) {\n const values = match[1]?.split(\",\").map(Number);\n if (values && values?.length >= 4) {\n scaleX = values[0]!;\n scaleY = values[3]!;\n }\n } else {\n const scaleXMatch = transform.match(/scaleX\\(([^)]+)\\)/);\n const scaleYMatch = transform.match(/scaleY\\(([^)]+)\\)/);\n if (scaleXMatch) scaleX = parseFloat(scaleXMatch[1]!);\n if (scaleYMatch) scaleY = parseFloat(scaleYMatch[1]!);\n }\n\n const unscaledWidth = scaledRect.width / scaleX;\n const unscaledHeight = scaledRect.height / scaleY;\n\n const unscaledX = scaledRect.x + (scaledRect.width - unscaledWidth) / 2;\n const unscaledY = scaledRect.y + (scaledRect.height - unscaledHeight) / 2;\n\n return {\n x: unscaledX,\n y: unscaledY,\n width: unscaledWidth,\n height: unscaledHeight,\n top: unscaledY,\n right: unscaledX + unscaledWidth,\n bottom: unscaledY + unscaledHeight,\n left: unscaledX,\n };\n }\n\n private log(...args: any[]) {\n if (this.options.debug) console.log(\"[TextMorph]\", ...args);\n }\n}\n"],"mappings":"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","names":["TextMorph","_TextMorph","options","anim","value","element","oldWidth","oldHeight","byWord","iterator","blocks","oldChildren","newIds","b","exiting","child","parentRect","rect","id","block","span","prev","nearest","s","sRect","cRect","nextPos","dx","dy","a","animation","newWidth","newHeight","children","measures","index","key","current","cx","cy","deltaX","deltaY","isNew","style","acc","string","x","scaledRect","transform","scaleX","scaleY","matrixRegex","match","values","scaleXMatch","scaleYMatch","unscaledWidth","unscaledHeight","unscaledX","unscaledY","args"]}
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { T as TextMorphOptions } from './types-CsvRfPun.mjs';
2
2
 
3
- var version = "0.0.3";
3
+ var version = "0.0.4";
4
4
 
5
5
  declare class TextMorph {
6
6
  private element;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { T as TextMorphOptions } from './types-CsvRfPun.js';
2
2
 
3
- var version = "0.0.3";
3
+ var version = "0.0.4";
4
4
 
5
5
  declare class TextMorph {
6
6
  private element;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- "use strict";var M=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var R=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var C=(o,e)=>{for(var t in e)M(o,t,{get:e[t],enumerable:!0})},j=(o,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of R(e))!k.call(o,i)&&i!==t&&M(o,i,{get:()=>e[i],enumerable:!(n=L(e,i))||n.enumerable});return o};var I=o=>j(M({},"__esModule",{value:!0}),o);var O={};C(O,{TextMorph:()=>x,version:()=>w});module.exports=I(O);var w="0.0.3";var x=class o{element;options={};data;currentMeasures={};prevMeasures={};isInitialRender=!0;static styleEl;constructor(e){this.options={locale:"en",duration:400,ease:"cubic-bezier(0.19, 1, 0.22, 1)",...e},this.element=e.element,this.element.setAttribute("torph-root",""),this.element.style.transitionDuration=`${this.options.duration}ms`,this.element.style.transitionTimingFunction=this.options.ease,e.debug&&this.element.setAttribute("torph-debug",""),this.data=this.element.innerHTML,this.addStyles()}destroy(){this.element.getAnimations().forEach(e=>e.cancel()),this.element.removeAttribute("torph-root"),this.element.removeAttribute("torph-debug"),this.removeStyles()}update(e){if(e!==this.data){if(this.data=e,this.data instanceof HTMLElement)throw new Error("HTMLElement not yet supported");this.createTextGroup(this.data,this.element)}}createTextGroup(e,t){let n=t.offsetWidth,i=t.offsetHeight,r=e.includes(" "),g=new Intl.Segmenter(this.options.locale,{granularity:r?"word":"grapheme"}).segment(e)[Symbol.iterator](),m=this.blocks(g);this.prevMeasures=this.measure();let h=Array.from(t.children),d=new Set(m.map(s=>s.id)),p=h.filter(s=>!d.has(s.getAttribute("torph-id"))&&!s.hasAttribute("torph-exiting")),u=this.getUnscaledBoundingClientRect(t);if(p.forEach(s=>{let a=this.getUnscaledBoundingClientRect(s);s.setAttribute("torph-exiting",""),s.style.position="absolute",s.style.pointerEvents="none",s.style.left=`${a.left-u.left}px`,s.style.top=`${a.top-u.top}px`,s.style.width=`${a.width}px`,s.style.height=`${a.height}px`}),h.forEach(s=>{let a=s.getAttribute("torph-id");d.has(a)&&s.remove()}),m.forEach(s=>{let a=document.createElement("span");a.setAttribute("torph-item",""),a.setAttribute("torph-id",s.id),a.textContent=s.string,t.appendChild(a)}),this.currentMeasures=this.measure(),this.updateStyles(),p.forEach(s=>{if(this.isInitialRender){s.remove();return}let a=s.getAttribute("torph-id"),v=this.prevMeasures[a],E=Array.from(t.children).find(b=>{let S=b.getBoundingClientRect(),$=s.getBoundingClientRect();return Math.abs(S.left-$.left)<40}),y=E?this.currentMeasures[E.getAttribute("torph-id")]:v,A=(y?y.x-(v?.x||0):0)*.5,T=(y?y.y-(v?.y||0):0)*.5;s.getAnimations().forEach(b=>b.cancel());let H=s.animate({transform:`translate(${A}px, ${T}px) scale(0.95)`,opacity:0,offset:1},{duration:this.options.duration,easing:this.options.ease,fill:"both"});H.onfinish=()=>s.remove()}),this.isInitialRender){this.isInitialRender=!1,t.style.width="auto",t.style.height="auto";return}if(n===0||i===0)return;t.style.width="auto",t.style.height="auto",t.offsetWidth;let c=t.offsetWidth,f=t.offsetHeight;t.style.width=`${n}px`,t.style.height=`${i}px`,t.offsetWidth,t.style.width=`${c}px`,t.style.height=`${f}px`,setTimeout(()=>{t.style.width="auto",t.style.height="auto"},this.options.duration)}measure(){let e=Array.from(this.element.children),t={};return e.forEach((n,i)=>{if(n.hasAttribute("torph-exiting"))return;let r=n.getAttribute("torph-id")||`child-${i}`;t[r]={x:n.offsetLeft,y:n.offsetTop}}),t}updateStyles(){if(this.isInitialRender)return;Array.from(this.element.children).forEach((t,n)=>{if(t.hasAttribute("torph-exiting"))return;let i=t.getAttribute("torph-id")||`child-${n}`,r=this.prevMeasures[i],l=this.currentMeasures[i],g=l?.x||0,m=l?.y||0,h=r?r?.x-g:0,d=r?r?.y-m:0,p=!r;t.getAnimations().forEach(u=>u.cancel()),t.animate({transform:`translate(${h}px, ${d}px) scale(${p?.95:1})`,opacity:p?0:1,offset:0},{duration:this.options.duration,easing:this.options.ease,delay:p?this.options.duration*.2:0,fill:"both"})})}addStyles(){if(o.styleEl)return;let e=document.createElement("style");e.dataset.torph="true",e.innerHTML=`
2
+ "use strict";var M=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var R=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var C=(o,e)=>{for(var t in e)M(o,t,{get:e[t],enumerable:!0})},j=(o,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of R(e))!k.call(o,i)&&i!==t&&M(o,i,{get:()=>e[i],enumerable:!(n=L(e,i))||n.enumerable});return o};var I=o=>j(M({},"__esModule",{value:!0}),o);var O={};C(O,{TextMorph:()=>x,version:()=>w});module.exports=I(O);var w="0.0.4";var x=class o{element;options={};data;currentMeasures={};prevMeasures={};isInitialRender=!0;static styleEl;constructor(e){this.options={locale:"en",duration:400,ease:"cubic-bezier(0.19, 1, 0.22, 1)",...e},this.element=e.element,this.element.setAttribute("torph-root",""),this.element.style.transitionDuration=`${this.options.duration}ms`,this.element.style.transitionTimingFunction=this.options.ease,e.debug&&this.element.setAttribute("torph-debug",""),this.data=this.element.innerHTML,this.addStyles()}destroy(){this.element.getAnimations().forEach(e=>e.cancel()),this.element.removeAttribute("torph-root"),this.element.removeAttribute("torph-debug"),this.removeStyles()}update(e){if(e!==this.data){if(this.data=e,this.data instanceof HTMLElement)throw new Error("HTMLElement not yet supported");this.createTextGroup(this.data,this.element)}}createTextGroup(e,t){let n=t.offsetWidth,i=t.offsetHeight,r=e.includes(" "),g=new Intl.Segmenter(this.options.locale,{granularity:r?"word":"grapheme"}).segment(e)[Symbol.iterator](),m=this.blocks(g);this.prevMeasures=this.measure();let h=Array.from(t.children),d=new Set(m.map(s=>s.id)),p=h.filter(s=>!d.has(s.getAttribute("torph-id"))&&!s.hasAttribute("torph-exiting")),u=this.getUnscaledBoundingClientRect(t);if(p.forEach(s=>{let a=this.getUnscaledBoundingClientRect(s);s.setAttribute("torph-exiting",""),s.style.position="absolute",s.style.pointerEvents="none",s.style.left=`${a.left-u.left}px`,s.style.top=`${a.top-u.top}px`,s.style.width=`${a.width}px`,s.style.height=`${a.height}px`}),h.forEach(s=>{let a=s.getAttribute("torph-id");d.has(a)&&s.remove()}),m.forEach(s=>{let a=document.createElement("span");a.setAttribute("torph-item",""),a.setAttribute("torph-id",s.id),a.textContent=s.string,t.appendChild(a)}),this.currentMeasures=this.measure(),this.updateStyles(),p.forEach(s=>{if(this.isInitialRender){s.remove();return}let a=s.getAttribute("torph-id"),v=this.prevMeasures[a],E=Array.from(t.children).find(b=>{let S=b.getBoundingClientRect(),$=s.getBoundingClientRect();return Math.abs(S.left-$.left)<40}),f=E?this.currentMeasures[E.getAttribute("torph-id")]:v,A=(f?f.x-(v?.x||0):0)*.5,T=(f?f.y-(v?.y||0):0)*.5;s.getAnimations().forEach(b=>b.cancel());let H=s.animate({transform:`translate(${A}px, ${T}px) scale(0.95)`,opacity:0,offset:1},{duration:this.options.duration,easing:this.options.ease,fill:"both"});H.onfinish=()=>s.remove()}),this.isInitialRender){this.isInitialRender=!1,t.style.width="auto",t.style.height="auto";return}if(n===0||i===0)return;t.style.width="auto",t.style.height="auto",t.offsetWidth;let c=t.offsetWidth,y=t.offsetHeight;t.style.width=`${n}px`,t.style.height=`${i}px`,t.offsetWidth,t.style.width=`${c}px`,t.style.height=`${y}px`,setTimeout(()=>{t.style.width="auto",t.style.height="auto"},this.options.duration)}measure(){let e=Array.from(this.element.children),t={};return e.forEach((n,i)=>{if(n.hasAttribute("torph-exiting"))return;let r=n.getAttribute("torph-id")||`child-${i}`;t[r]={x:n.offsetLeft,y:n.offsetTop}}),t}updateStyles(){if(this.isInitialRender)return;Array.from(this.element.children).forEach((t,n)=>{if(t.hasAttribute("torph-exiting"))return;let i=t.getAttribute("torph-id")||`child-${n}`,r=this.prevMeasures[i],l=this.currentMeasures[i],g=l?.x||0,m=l?.y||0,h=r?r?.x-g:0,d=r?r?.y-m:0,p=!r;t.getAnimations().forEach(u=>u.cancel()),t.animate({transform:`translate(${h}px, ${d}px) scale(${p?.95:1})`,opacity:p?0:1,offset:0},{duration:this.options.duration,easing:this.options.ease,delay:p?this.options.duration*.2:0,fill:"both"})})}addStyles(){if(o.styleEl)return;let e=document.createElement("style");e.dataset.torph="true",e.innerHTML=`
3
3
  [torph-root] {
4
4
  display: inline-flex; /* TODO: remove for multi-line support */
5
5
  position: relative;
@@ -21,5 +21,5 @@
21
21
  outline-offset: -4px;
22
22
  }
23
23
  }
24
- `,document.head.appendChild(e),o.styleEl=e}removeStyles(){o.styleEl&&(o.styleEl.remove(),o.styleEl=void 0)}blocks(e){return Array.from(e).reduce((n,i)=>i.segment===" "?[...n,{id:`space-${i.index}`,string:"\xA0"}]:n.find(l=>l.string===i.segment)?[...n,{id:`${i.segment}-${i.index}`,string:i.segment}]:[...n,{id:i.segment,string:i.segment}],[])}getUnscaledBoundingClientRect(e){let t=e.getBoundingClientRect(),i=window.getComputedStyle(e).transform,r=1,l=1,g=/matrix\(([^)]+)\)/,m=i.match(g);if(m){let c=m[1]?.split(",").map(Number);c&&c?.length>=4&&(r=c[0],l=c[3])}else{let c=i.match(/scaleX\(([^)]+)\)/),f=i.match(/scaleY\(([^)]+)\)/);c&&(r=parseFloat(c[1])),f&&(l=parseFloat(f[1]))}let h=t.width/r,d=t.height/l,p=t.x+(t.width-h)/2,u=t.y+(t.height-d)/2;return{x:p,y:u,width:h,height:d,top:u,right:p+h,bottom:u+d,left:p}}log(...e){this.options.debug&&console.log("[TextMorph]",...e)}};0&&(module.exports={TextMorph,version});
24
+ `,document.head.appendChild(e),o.styleEl=e}removeStyles(){o.styleEl&&(o.styleEl.remove(),o.styleEl=void 0)}blocks(e){return Array.from(e).reduce((n,i)=>i.segment===" "?[...n,{id:`space-${i.index}`,string:"\xA0"}]:n.find(l=>l.string===i.segment)?[...n,{id:`${i.segment}-${i.index}`,string:i.segment}]:[...n,{id:i.segment,string:i.segment}],[])}getUnscaledBoundingClientRect(e){let t=e.getBoundingClientRect(),i=window.getComputedStyle(e).transform,r=1,l=1,g=/matrix\(([^)]+)\)/,m=i.match(g);if(m){let c=m[1]?.split(",").map(Number);c&&c?.length>=4&&(r=c[0],l=c[3])}else{let c=i.match(/scaleX\(([^)]+)\)/),y=i.match(/scaleY\(([^)]+)\)/);c&&(r=parseFloat(c[1])),y&&(l=parseFloat(y[1]))}let h=t.width/r,d=t.height/l,p=t.x+(t.width-h)/2,u=t.y+(t.height-d)/2;return{x:p,y:u,width:h,height:d,top:u,right:p+h,bottom:u+d,left:p}}log(...e){this.options.debug&&console.log("[TextMorph]",...e)}};0&&(module.exports={TextMorph,version});
25
25
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../package.json","../src/lib/text-morph/index.ts"],"sourcesContent":["export { version } from \"./../package.json\";\n\nexport { TextMorph } from \"./lib/text-morph\";\nexport type { TextMorphOptions } from \"./lib/text-morph/types\";\n","{\n \"name\": \"torph\",\n \"version\": \"0.0.3\",\n \"description\": \"Dependency-free animated text component.\",\n \"author\": \"Lochie Axon\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/lochie/torph.git\",\n \"directory\": \"packages/torph\"\n },\n \"types\": \"dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.js\"\n },\n \"./react\": {\n \"types\": \"./dist/react/index.d.ts\",\n \"import\": \"./dist/react/index.mjs\",\n \"require\": \"./dist/react/index.js\"\n },\n \"./vue\": {\n \"types\": \"./dist/vue/index.d.ts\",\n \"import\": \"./dist/vue/index.mjs\",\n \"require\": \"./dist/vue/index.js\"\n },\n \"./svelte\": {\n \"types\": \"./dist/svelte/index.d.ts\",\n \"import\": \"./dist/svelte/index.mjs\",\n \"require\": \"./dist/svelte/index.js\"\n }\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"lint\": \"eslint -c .eslintrc.cjs ./src/**/*.{ts,tsx}\",\n \"lint:fix\": \"eslint --fix -c .eslintrc.cjs ./src/**/*.{ts,tsx}\",\n \"pre-commit\": \"lint-staged\"\n },\n \"keywords\": [\n \"react\",\n \"vue\",\n \"svelte\",\n \"text\",\n \"animation\",\n \"morph\"\n ],\n \"bugs\": {\n \"url\": \"https://github.com/lochie/torph/issues\"\n },\n \"files\": [\n \"dist/*\"\n ],\n \"peerDependencies\": {\n \"react\": \">=18\",\n \"react-dom\": \">=18\",\n \"vue\": \">=3\",\n \"svelte\": \">=4\"\n },\n \"peerDependenciesMeta\": {\n \"react\": {\n \"optional\": true\n },\n \"react-dom\": {\n \"optional\": true\n },\n \"vue\": {\n \"optional\": true\n },\n \"svelte\": {\n \"optional\": true\n }\n },\n \"devDependencies\": {\n \"@types/react\": \"^18.2.15\",\n \"@typescript-eslint/eslint-plugin\": \"^6.8.0\",\n \"@typescript-eslint/parser\": \"^6.8.0\",\n \"eslint\": \"^9.36.0\",\n \"eslint-config-airbnb\": \"^19.0.4\",\n \"eslint-config-airbnb-typescript\": \"^17.1.0\",\n \"eslint-config-prettier\": \"^9.0.0\",\n \"eslint-plugin-import\": \"^2.28.1\",\n \"eslint-plugin-jsx-a11y\": \"^6.7.1\",\n \"eslint-plugin-prettier\": \"^5.0.1\",\n \"eslint-plugin-react\": \"^7.33.2\",\n \"eslint-plugin-react-hooks\": \"^4.6.0\",\n \"prettier\": \"^3.0.3\",\n \"react\": \"^18.2.0\",\n \"react-dom\": \"^18.2.0\",\n \"svelte\": \"^4.0.0\",\n \"tsup\": \"^8.5.0\",\n \"typescript\": \"^5.9.3\",\n \"vue\": \"^3.3.0\"\n }\n}\n","import type { TextMorphOptions } from \"./types\";\n\nexport { TextMorphOptions } from \"./types\";\n\ntype Block = {\n id: string;\n string: string;\n};\ntype Measures = {\n [key: string]: { x: number; y: number };\n};\n\nexport class TextMorph {\n private element: HTMLElement;\n private options: Omit<TextMorphOptions, \"element\"> = {};\n\n private data: HTMLElement | string;\n\n private currentMeasures: Measures = {};\n private prevMeasures: Measures = {};\n private isInitialRender = true;\n\n static styleEl: HTMLStyleElement;\n\n constructor(options: TextMorphOptions) {\n this.options = {\n locale: \"en\",\n duration: 400,\n ease: \"cubic-bezier(0.19, 1, 0.22, 1)\",\n ...options,\n };\n\n this.element = options.element;\n this.element.setAttribute(\"torph-root\", \"\");\n this.element.style.transitionDuration = `${this.options.duration}ms`;\n this.element.style.transitionTimingFunction = this.options.ease!;\n\n if (options.debug) this.element.setAttribute(\"torph-debug\", \"\");\n\n this.data = this.element.innerHTML;\n\n this.addStyles();\n }\n\n destroy() {\n this.element.getAnimations().forEach((anim) => anim.cancel());\n this.element.removeAttribute(\"torph-root\");\n this.element.removeAttribute(\"torph-debug\");\n this.removeStyles();\n }\n\n update(value: HTMLElement | string) {\n if (value === this.data) return;\n this.data = value;\n\n if (this.data instanceof HTMLElement) {\n // TODO: handle HTMLElement case\n throw new Error(\"HTMLElement not yet supported\");\n } else {\n this.createTextGroup(this.data, this.element);\n }\n }\n\n private createTextGroup(value: string, element: HTMLElement) {\n const oldWidth = element.offsetWidth;\n const oldHeight = element.offsetHeight;\n\n const byWord = value.includes(\" \");\n const segmenter = new Intl.Segmenter(this.options.locale, {\n granularity: byWord ? \"word\" : \"grapheme\",\n });\n const iterator = segmenter.segment(value)[Symbol.iterator]();\n const blocks = this.blocks(iterator);\n\n this.prevMeasures = this.measure();\n const oldChildren = Array.from(element.children) as HTMLElement[];\n const newIds = new Set(blocks.map((b) => b.id));\n\n const exiting = oldChildren.filter(\n (child) =>\n !newIds.has(child.getAttribute(\"torph-id\") as string) &&\n !child.hasAttribute(\"torph-exiting\"),\n );\n\n const parentRect = this.getUnscaledBoundingClientRect(element);\n exiting.forEach((child) => {\n const rect = this.getUnscaledBoundingClientRect(child);\n child.setAttribute(\"torph-exiting\", \"\");\n child.style.position = \"absolute\";\n child.style.pointerEvents = \"none\";\n child.style.left = `${rect.left - parentRect.left}px`;\n child.style.top = `${rect.top - parentRect.top}px`;\n child.style.width = `${rect.width}px`;\n child.style.height = `${rect.height}px`;\n });\n\n oldChildren.forEach((child) => {\n const id = child.getAttribute(\"torph-id\") as string;\n if (newIds.has(id)) child.remove();\n });\n\n blocks.forEach((block) => {\n const span = document.createElement(\"span\");\n span.setAttribute(\"torph-item\", \"\");\n span.setAttribute(\"torph-id\", block.id);\n span.textContent = block.string;\n element.appendChild(span);\n });\n\n this.currentMeasures = this.measure();\n this.updateStyles();\n\n exiting.forEach((child) => {\n if (this.isInitialRender) {\n child.remove();\n return;\n }\n\n const id = child.getAttribute(\"torph-id\")!;\n\n const prev = this.prevMeasures[id];\n\n const siblings = Array.from(element.children) as HTMLElement[];\n const nearest = siblings.find((s) => {\n const sRect = s.getBoundingClientRect();\n const cRect = child.getBoundingClientRect();\n return Math.abs(sRect.left - cRect.left) < 40;\n });\n\n const nextPos = nearest\n ? this.currentMeasures[nearest.getAttribute(\"torph-id\")!]\n : prev;\n\n const dx = (nextPos ? nextPos.x - (prev?.x || 0) : 0) * 0.5;\n const dy = (nextPos ? nextPos.y - (prev?.y || 0) : 0) * 0.5;\n\n child.getAnimations().forEach((a) => a.cancel());\n const animation: Animation = child.animate(\n {\n transform: `translate(${dx}px, ${dy}px) scale(0.95)`,\n opacity: 0,\n offset: 1,\n },\n {\n duration: this.options.duration,\n easing: this.options.ease,\n fill: \"both\",\n },\n );\n animation.onfinish = () => child.remove();\n });\n\n if (this.isInitialRender) {\n this.isInitialRender = false;\n element.style.width = \"auto\";\n element.style.height = \"auto\";\n return;\n }\n\n if (oldWidth === 0 || oldHeight === 0) return;\n\n element.style.width = \"auto\";\n element.style.height = \"auto\";\n void element.offsetWidth; // force reflow\n\n const newWidth = element.offsetWidth;\n const newHeight = element.offsetHeight;\n\n element.style.width = `${oldWidth}px`;\n element.style.height = `${oldHeight}px`;\n void element.offsetWidth; // force reflow\n\n element.style.width = `${newWidth}px`;\n element.style.height = `${newHeight}px`;\n\n // TODO: move to `transitionend` event listener\n setTimeout(() => {\n element.style.width = \"auto\";\n element.style.height = \"auto\";\n }, this.options.duration);\n }\n\n private measure() {\n const children = Array.from(this.element.children) as HTMLElement[];\n const measures: Measures = {};\n\n children.forEach((child, index) => {\n if (child.hasAttribute(\"torph-exiting\")) return;\n const key = child.getAttribute(\"torph-id\") || `child-${index}`;\n measures[key] = {\n x: child.offsetLeft,\n y: child.offsetTop,\n };\n });\n\n return measures;\n }\n\n private updateStyles() {\n if (this.isInitialRender) return;\n\n const children = Array.from(this.element.children) as HTMLElement[];\n\n children.forEach((child, index) => {\n if (child.hasAttribute(\"torph-exiting\")) return;\n const key = child.getAttribute(\"torph-id\") || `child-${index}`;\n const prev = this.prevMeasures[key];\n const current = this.currentMeasures[key];\n\n const cx = current?.x || 0;\n const cy = current?.y || 0;\n\n const deltaX = prev ? prev?.x - cx : 0;\n const deltaY = prev ? prev?.y - cy : 0;\n const isNew = !prev;\n\n child.getAnimations().forEach((a) => a.cancel());\n child.animate(\n {\n transform: `translate(${deltaX}px, ${deltaY}px) scale(${isNew ? 0.95 : 1})`,\n opacity: isNew ? 0 : 1,\n offset: 0,\n },\n {\n duration: this.options.duration,\n easing: this.options.ease,\n delay: isNew ? this.options.duration! * 0.2 : 0,\n fill: \"both\",\n },\n );\n });\n }\n\n private addStyles() {\n if (TextMorph.styleEl) return;\n\n const style = document.createElement(\"style\");\n style.dataset.torph = \"true\";\n style.innerHTML = `\n[torph-root] {\n display: inline-flex; /* TODO: remove for multi-line support */\n position: relative;\n will-change: width, height;\n transition-property: width, height;\n}\n\n[torph-item] {\n display: inline-block;\n will-change: opacity, transform;\n transform: none;\n opacity: 1;\n}\n \n[torph-root][torph-debug] {\n outline:2px solid magenta;\n [torph-item] {\n outline:2px solid cyan;\n outline-offset: -4px;\n }\n}\n `;\n document.head.appendChild(style);\n TextMorph.styleEl = style;\n }\n\n private removeStyles() {\n if (TextMorph.styleEl) {\n TextMorph.styleEl.remove();\n TextMorph.styleEl = undefined!;\n }\n }\n\n // utils\n\n private blocks(iterator: Intl.SegmentIterator<Intl.SegmentData>) {\n const uniqueStrings: Block[] = Array.from(iterator).reduce(\n (acc, string) => {\n if (string.segment === \" \") {\n return [...acc, { id: `space-${string.index}`, string: \"\\u00A0\" }];\n }\n\n const existingString = acc.find((x) => x.string === string.segment);\n if (existingString) {\n return [\n ...acc,\n { id: `${string.segment}-${string.index}`, string: string.segment },\n ];\n }\n\n return [\n ...acc,\n {\n id: string.segment,\n string: string.segment,\n },\n ];\n },\n [] as Block[],\n );\n\n return uniqueStrings;\n }\n\n private getUnscaledBoundingClientRect(element: HTMLElement) {\n const scaledRect = element.getBoundingClientRect();\n const computedStyle = window.getComputedStyle(element);\n const transform = computedStyle.transform;\n\n let scaleX = 1;\n let scaleY = 1;\n\n const matrixRegex = /matrix\\(([^)]+)\\)/;\n const match = transform.match(matrixRegex);\n\n if (match) {\n const values = match[1]?.split(\",\").map(Number);\n if (values && values?.length >= 4) {\n scaleX = values[0]!;\n scaleY = values[3]!;\n }\n } else {\n const scaleXMatch = transform.match(/scaleX\\(([^)]+)\\)/);\n const scaleYMatch = transform.match(/scaleY\\(([^)]+)\\)/);\n if (scaleXMatch) scaleX = parseFloat(scaleXMatch[1]!);\n if (scaleYMatch) scaleY = parseFloat(scaleYMatch[1]!);\n }\n\n const unscaledWidth = scaledRect.width / scaleX;\n const unscaledHeight = scaledRect.height / scaleY;\n\n const unscaledX = scaledRect.x + (scaledRect.width - unscaledWidth) / 2;\n const unscaledY = scaledRect.y + (scaledRect.height - unscaledHeight) / 2;\n\n return {\n x: unscaledX,\n y: unscaledY,\n width: unscaledWidth,\n height: unscaledHeight,\n top: unscaledY,\n right: unscaledX + unscaledWidth,\n bottom: unscaledY + unscaledHeight,\n left: unscaledX,\n };\n }\n\n private log(...args: any[]) {\n if (this.options.debug) console.log(\"[TextMorph]\", ...args);\n }\n}\n"],"mappings":"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","names":["src_exports","__export","TextMorph","version","__toCommonJS","version","TextMorph","_TextMorph","options","anim","value","element","oldWidth","oldHeight","byWord","iterator","blocks","oldChildren","newIds","b","exiting","child","parentRect","rect","id","block","span","prev","nearest","s","sRect","cRect","nextPos","dx","dy","a","animation","newWidth","newHeight","children","measures","index","key","current","cx","cy","deltaX","deltaY","isNew","style","acc","string","x","scaledRect","transform","scaleX","scaleY","matrixRegex","match","values","scaleXMatch","scaleYMatch","unscaledWidth","unscaledHeight","unscaledX","unscaledY","args"]}
1
+ {"version":3,"sources":["../src/index.ts","../package.json","../src/lib/text-morph/index.ts"],"sourcesContent":["export { version } from \"./../package.json\";\n\nexport { TextMorph } from \"./lib/text-morph\";\nexport type { TextMorphOptions } from \"./lib/text-morph/types\";\n","{\n \"name\": \"torph\",\n \"version\": \"0.0.4\",\n \"description\": \"Dependency-free animated text component.\",\n \"author\": \"Lochie Axon\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/lochie/torph.git\",\n \"directory\": \"packages/torph\"\n },\n \"types\": \"dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.js\"\n },\n \"./react\": {\n \"types\": \"./dist/react/index.d.ts\",\n \"import\": \"./dist/react/index.mjs\",\n \"require\": \"./dist/react/index.js\"\n },\n \"./vue\": {\n \"types\": \"./dist/vue/index.d.ts\",\n \"import\": \"./dist/vue/index.mjs\",\n \"require\": \"./dist/vue/index.js\"\n },\n \"./svelte\": {\n \"types\": \"./dist/svelte/index.d.ts\",\n \"import\": \"./dist/svelte/index.mjs\",\n \"require\": \"./dist/svelte/index.js\"\n }\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"lint\": \"eslint -c .eslintrc.cjs ./src/**/*.{ts,tsx}\",\n \"lint:fix\": \"eslint --fix -c .eslintrc.cjs ./src/**/*.{ts,tsx}\",\n \"pre-commit\": \"lint-staged\"\n },\n \"keywords\": [\n \"react\",\n \"vue\",\n \"svelte\",\n \"text\",\n \"animation\",\n \"morph\"\n ],\n \"bugs\": {\n \"url\": \"https://github.com/lochie/torph/issues\"\n },\n \"files\": [\n \"dist/*\"\n ],\n \"peerDependencies\": {\n \"react\": \">=18\",\n \"react-dom\": \">=18\",\n \"vue\": \">=3\",\n \"svelte\": \">=4\"\n },\n \"peerDependenciesMeta\": {\n \"react\": {\n \"optional\": true\n },\n \"react-dom\": {\n \"optional\": true\n },\n \"vue\": {\n \"optional\": true\n },\n \"svelte\": {\n \"optional\": true\n }\n },\n \"devDependencies\": {\n \"@types/react\": \"^18.2.15\",\n \"@typescript-eslint/eslint-plugin\": \"^6.8.0\",\n \"@typescript-eslint/parser\": \"^6.8.0\",\n \"eslint\": \"^9.36.0\",\n \"eslint-config-airbnb\": \"^19.0.4\",\n \"eslint-config-airbnb-typescript\": \"^17.1.0\",\n \"eslint-config-prettier\": \"^9.0.0\",\n \"eslint-plugin-import\": \"^2.28.1\",\n \"eslint-plugin-jsx-a11y\": \"^6.7.1\",\n \"eslint-plugin-prettier\": \"^5.0.1\",\n \"eslint-plugin-react\": \"^7.33.2\",\n \"eslint-plugin-react-hooks\": \"^4.6.0\",\n \"prettier\": \"^3.0.3\",\n \"react\": \"^18.2.0\",\n \"react-dom\": \"^18.2.0\",\n \"svelte\": \"^4.0.0\",\n \"tsup\": \"^8.5.0\",\n \"typescript\": \"^5.9.3\",\n \"vue\": \"^3.3.0\"\n }\n}\n","import type { TextMorphOptions } from \"./types\";\n\nexport type { TextMorphOptions } from \"./types\";\n\ntype Block = {\n id: string;\n string: string;\n};\ntype Measures = {\n [key: string]: { x: number; y: number };\n};\n\nexport class TextMorph {\n private element: HTMLElement;\n private options: Omit<TextMorphOptions, \"element\"> = {};\n\n private data: HTMLElement | string;\n\n private currentMeasures: Measures = {};\n private prevMeasures: Measures = {};\n private isInitialRender = true;\n\n static styleEl: HTMLStyleElement;\n\n constructor(options: TextMorphOptions) {\n this.options = {\n locale: \"en\",\n duration: 400,\n ease: \"cubic-bezier(0.19, 1, 0.22, 1)\",\n ...options,\n };\n\n this.element = options.element;\n this.element.setAttribute(\"torph-root\", \"\");\n this.element.style.transitionDuration = `${this.options.duration}ms`;\n this.element.style.transitionTimingFunction = this.options.ease!;\n\n if (options.debug) this.element.setAttribute(\"torph-debug\", \"\");\n\n this.data = this.element.innerHTML;\n\n this.addStyles();\n }\n\n destroy() {\n this.element.getAnimations().forEach((anim) => anim.cancel());\n this.element.removeAttribute(\"torph-root\");\n this.element.removeAttribute(\"torph-debug\");\n this.removeStyles();\n }\n\n update(value: HTMLElement | string) {\n if (value === this.data) return;\n this.data = value;\n\n if (this.data instanceof HTMLElement) {\n // TODO: handle HTMLElement case\n throw new Error(\"HTMLElement not yet supported\");\n } else {\n this.createTextGroup(this.data, this.element);\n }\n }\n\n private createTextGroup(value: string, element: HTMLElement) {\n const oldWidth = element.offsetWidth;\n const oldHeight = element.offsetHeight;\n\n const byWord = value.includes(\" \");\n const segmenter = new Intl.Segmenter(this.options.locale, {\n granularity: byWord ? \"word\" : \"grapheme\",\n });\n const iterator = segmenter.segment(value)[Symbol.iterator]();\n const blocks = this.blocks(iterator);\n\n this.prevMeasures = this.measure();\n const oldChildren = Array.from(element.children) as HTMLElement[];\n const newIds = new Set(blocks.map((b) => b.id));\n\n const exiting = oldChildren.filter(\n (child) =>\n !newIds.has(child.getAttribute(\"torph-id\") as string) &&\n !child.hasAttribute(\"torph-exiting\"),\n );\n\n const parentRect = this.getUnscaledBoundingClientRect(element);\n exiting.forEach((child) => {\n const rect = this.getUnscaledBoundingClientRect(child);\n child.setAttribute(\"torph-exiting\", \"\");\n child.style.position = \"absolute\";\n child.style.pointerEvents = \"none\";\n child.style.left = `${rect.left - parentRect.left}px`;\n child.style.top = `${rect.top - parentRect.top}px`;\n child.style.width = `${rect.width}px`;\n child.style.height = `${rect.height}px`;\n });\n\n oldChildren.forEach((child) => {\n const id = child.getAttribute(\"torph-id\") as string;\n if (newIds.has(id)) child.remove();\n });\n\n blocks.forEach((block) => {\n const span = document.createElement(\"span\");\n span.setAttribute(\"torph-item\", \"\");\n span.setAttribute(\"torph-id\", block.id);\n span.textContent = block.string;\n element.appendChild(span);\n });\n\n this.currentMeasures = this.measure();\n this.updateStyles();\n\n exiting.forEach((child) => {\n if (this.isInitialRender) {\n child.remove();\n return;\n }\n\n const id = child.getAttribute(\"torph-id\")!;\n\n const prev = this.prevMeasures[id];\n\n const siblings = Array.from(element.children) as HTMLElement[];\n const nearest = siblings.find((s) => {\n const sRect = s.getBoundingClientRect();\n const cRect = child.getBoundingClientRect();\n return Math.abs(sRect.left - cRect.left) < 40;\n });\n\n const nextPos = nearest\n ? this.currentMeasures[nearest.getAttribute(\"torph-id\")!]\n : prev;\n\n const dx = (nextPos ? nextPos.x - (prev?.x || 0) : 0) * 0.5;\n const dy = (nextPos ? nextPos.y - (prev?.y || 0) : 0) * 0.5;\n\n child.getAnimations().forEach((a) => a.cancel());\n const animation: Animation = child.animate(\n {\n transform: `translate(${dx}px, ${dy}px) scale(0.95)`,\n opacity: 0,\n offset: 1,\n },\n {\n duration: this.options.duration,\n easing: this.options.ease,\n fill: \"both\",\n },\n );\n animation.onfinish = () => child.remove();\n });\n\n if (this.isInitialRender) {\n this.isInitialRender = false;\n element.style.width = \"auto\";\n element.style.height = \"auto\";\n return;\n }\n\n if (oldWidth === 0 || oldHeight === 0) return;\n\n element.style.width = \"auto\";\n element.style.height = \"auto\";\n void element.offsetWidth; // force reflow\n\n const newWidth = element.offsetWidth;\n const newHeight = element.offsetHeight;\n\n element.style.width = `${oldWidth}px`;\n element.style.height = `${oldHeight}px`;\n void element.offsetWidth; // force reflow\n\n element.style.width = `${newWidth}px`;\n element.style.height = `${newHeight}px`;\n\n // TODO: move to `transitionend` event listener\n setTimeout(() => {\n element.style.width = \"auto\";\n element.style.height = \"auto\";\n }, this.options.duration);\n }\n\n private measure() {\n const children = Array.from(this.element.children) as HTMLElement[];\n const measures: Measures = {};\n\n children.forEach((child, index) => {\n if (child.hasAttribute(\"torph-exiting\")) return;\n const key = child.getAttribute(\"torph-id\") || `child-${index}`;\n measures[key] = {\n x: child.offsetLeft,\n y: child.offsetTop,\n };\n });\n\n return measures;\n }\n\n private updateStyles() {\n if (this.isInitialRender) return;\n\n const children = Array.from(this.element.children) as HTMLElement[];\n\n children.forEach((child, index) => {\n if (child.hasAttribute(\"torph-exiting\")) return;\n const key = child.getAttribute(\"torph-id\") || `child-${index}`;\n const prev = this.prevMeasures[key];\n const current = this.currentMeasures[key];\n\n const cx = current?.x || 0;\n const cy = current?.y || 0;\n\n const deltaX = prev ? prev?.x - cx : 0;\n const deltaY = prev ? prev?.y - cy : 0;\n const isNew = !prev;\n\n child.getAnimations().forEach((a) => a.cancel());\n child.animate(\n {\n transform: `translate(${deltaX}px, ${deltaY}px) scale(${isNew ? 0.95 : 1})`,\n opacity: isNew ? 0 : 1,\n offset: 0,\n },\n {\n duration: this.options.duration,\n easing: this.options.ease,\n delay: isNew ? this.options.duration! * 0.2 : 0,\n fill: \"both\",\n },\n );\n });\n }\n\n private addStyles() {\n if (TextMorph.styleEl) return;\n\n const style = document.createElement(\"style\");\n style.dataset.torph = \"true\";\n style.innerHTML = `\n[torph-root] {\n display: inline-flex; /* TODO: remove for multi-line support */\n position: relative;\n will-change: width, height;\n transition-property: width, height;\n}\n\n[torph-item] {\n display: inline-block;\n will-change: opacity, transform;\n transform: none;\n opacity: 1;\n}\n \n[torph-root][torph-debug] {\n outline:2px solid magenta;\n [torph-item] {\n outline:2px solid cyan;\n outline-offset: -4px;\n }\n}\n `;\n document.head.appendChild(style);\n TextMorph.styleEl = style;\n }\n\n private removeStyles() {\n if (TextMorph.styleEl) {\n TextMorph.styleEl.remove();\n TextMorph.styleEl = undefined!;\n }\n }\n\n // utils\n\n private blocks(iterator: Intl.SegmentIterator<Intl.SegmentData>) {\n const uniqueStrings: Block[] = Array.from(iterator).reduce(\n (acc, string) => {\n if (string.segment === \" \") {\n return [...acc, { id: `space-${string.index}`, string: \"\\u00A0\" }];\n }\n\n const existingString = acc.find((x) => x.string === string.segment);\n if (existingString) {\n return [\n ...acc,\n { id: `${string.segment}-${string.index}`, string: string.segment },\n ];\n }\n\n return [\n ...acc,\n {\n id: string.segment,\n string: string.segment,\n },\n ];\n },\n [] as Block[],\n );\n\n return uniqueStrings;\n }\n\n private getUnscaledBoundingClientRect(element: HTMLElement) {\n const scaledRect = element.getBoundingClientRect();\n const computedStyle = window.getComputedStyle(element);\n const transform = computedStyle.transform;\n\n let scaleX = 1;\n let scaleY = 1;\n\n const matrixRegex = /matrix\\(([^)]+)\\)/;\n const match = transform.match(matrixRegex);\n\n if (match) {\n const values = match[1]?.split(\",\").map(Number);\n if (values && values?.length >= 4) {\n scaleX = values[0]!;\n scaleY = values[3]!;\n }\n } else {\n const scaleXMatch = transform.match(/scaleX\\(([^)]+)\\)/);\n const scaleYMatch = transform.match(/scaleY\\(([^)]+)\\)/);\n if (scaleXMatch) scaleX = parseFloat(scaleXMatch[1]!);\n if (scaleYMatch) scaleY = parseFloat(scaleYMatch[1]!);\n }\n\n const unscaledWidth = scaledRect.width / scaleX;\n const unscaledHeight = scaledRect.height / scaleY;\n\n const unscaledX = scaledRect.x + (scaledRect.width - unscaledWidth) / 2;\n const unscaledY = scaledRect.y + (scaledRect.height - unscaledHeight) / 2;\n\n return {\n x: unscaledX,\n y: unscaledY,\n width: unscaledWidth,\n height: unscaledHeight,\n top: unscaledY,\n right: unscaledX + unscaledWidth,\n bottom: unscaledY + unscaledHeight,\n left: unscaledX,\n };\n }\n\n private log(...args: any[]) {\n if (this.options.debug) console.log(\"[TextMorph]\", ...args);\n }\n}\n"],"mappings":"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","names":["src_exports","__export","TextMorph","version","__toCommonJS","version","TextMorph","_TextMorph","options","anim","value","element","oldWidth","oldHeight","byWord","iterator","blocks","oldChildren","newIds","b","exiting","child","parentRect","rect","id","block","span","prev","nearest","s","sRect","cRect","nextPos","dx","dy","a","animation","newWidth","newHeight","children","measures","index","key","current","cx","cy","deltaX","deltaY","isNew","style","acc","string","x","scaledRect","transform","scaleX","scaleY","matrixRegex","match","values","scaleXMatch","scaleYMatch","unscaledWidth","unscaledHeight","unscaledX","unscaledY","args"]}
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  "use client";
2
- import{a as e}from"./chunk-43OPPES3.mjs";var i="0.0.3";export{e as TextMorph,i as version};
2
+ import{a as e}from"./chunk-4ZXQ2BQA.mjs";var i="0.0.4";export{e as TextMorph,i as version};
3
3
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../package.json"],"sourcesContent":["{\n \"name\": \"torph\",\n \"version\": \"0.0.3\",\n \"description\": \"Dependency-free animated text component.\",\n \"author\": \"Lochie Axon\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/lochie/torph.git\",\n \"directory\": \"packages/torph\"\n },\n \"types\": \"dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.js\"\n },\n \"./react\": {\n \"types\": \"./dist/react/index.d.ts\",\n \"import\": \"./dist/react/index.mjs\",\n \"require\": \"./dist/react/index.js\"\n },\n \"./vue\": {\n \"types\": \"./dist/vue/index.d.ts\",\n \"import\": \"./dist/vue/index.mjs\",\n \"require\": \"./dist/vue/index.js\"\n },\n \"./svelte\": {\n \"types\": \"./dist/svelte/index.d.ts\",\n \"import\": \"./dist/svelte/index.mjs\",\n \"require\": \"./dist/svelte/index.js\"\n }\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"lint\": \"eslint -c .eslintrc.cjs ./src/**/*.{ts,tsx}\",\n \"lint:fix\": \"eslint --fix -c .eslintrc.cjs ./src/**/*.{ts,tsx}\",\n \"pre-commit\": \"lint-staged\"\n },\n \"keywords\": [\n \"react\",\n \"vue\",\n \"svelte\",\n \"text\",\n \"animation\",\n \"morph\"\n ],\n \"bugs\": {\n \"url\": \"https://github.com/lochie/torph/issues\"\n },\n \"files\": [\n \"dist/*\"\n ],\n \"peerDependencies\": {\n \"react\": \">=18\",\n \"react-dom\": \">=18\",\n \"vue\": \">=3\",\n \"svelte\": \">=4\"\n },\n \"peerDependenciesMeta\": {\n \"react\": {\n \"optional\": true\n },\n \"react-dom\": {\n \"optional\": true\n },\n \"vue\": {\n \"optional\": true\n },\n \"svelte\": {\n \"optional\": true\n }\n },\n \"devDependencies\": {\n \"@types/react\": \"^18.2.15\",\n \"@typescript-eslint/eslint-plugin\": \"^6.8.0\",\n \"@typescript-eslint/parser\": \"^6.8.0\",\n \"eslint\": \"^9.36.0\",\n \"eslint-config-airbnb\": \"^19.0.4\",\n \"eslint-config-airbnb-typescript\": \"^17.1.0\",\n \"eslint-config-prettier\": \"^9.0.0\",\n \"eslint-plugin-import\": \"^2.28.1\",\n \"eslint-plugin-jsx-a11y\": \"^6.7.1\",\n \"eslint-plugin-prettier\": \"^5.0.1\",\n \"eslint-plugin-react\": \"^7.33.2\",\n \"eslint-plugin-react-hooks\": \"^4.6.0\",\n \"prettier\": \"^3.0.3\",\n \"react\": \"^18.2.0\",\n \"react-dom\": \"^18.2.0\",\n \"svelte\": \"^4.0.0\",\n \"tsup\": \"^8.5.0\",\n \"typescript\": \"^5.9.3\",\n \"vue\": \"^3.3.0\"\n }\n}\n"],"mappings":";yCAEE,IAAAA,EAAW","names":["version"]}
1
+ {"version":3,"sources":["../package.json"],"sourcesContent":["{\n \"name\": \"torph\",\n \"version\": \"0.0.4\",\n \"description\": \"Dependency-free animated text component.\",\n \"author\": \"Lochie Axon\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/lochie/torph.git\",\n \"directory\": \"packages/torph\"\n },\n \"types\": \"dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.js\"\n },\n \"./react\": {\n \"types\": \"./dist/react/index.d.ts\",\n \"import\": \"./dist/react/index.mjs\",\n \"require\": \"./dist/react/index.js\"\n },\n \"./vue\": {\n \"types\": \"./dist/vue/index.d.ts\",\n \"import\": \"./dist/vue/index.mjs\",\n \"require\": \"./dist/vue/index.js\"\n },\n \"./svelte\": {\n \"types\": \"./dist/svelte/index.d.ts\",\n \"import\": \"./dist/svelte/index.mjs\",\n \"require\": \"./dist/svelte/index.js\"\n }\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"lint\": \"eslint -c .eslintrc.cjs ./src/**/*.{ts,tsx}\",\n \"lint:fix\": \"eslint --fix -c .eslintrc.cjs ./src/**/*.{ts,tsx}\",\n \"pre-commit\": \"lint-staged\"\n },\n \"keywords\": [\n \"react\",\n \"vue\",\n \"svelte\",\n \"text\",\n \"animation\",\n \"morph\"\n ],\n \"bugs\": {\n \"url\": \"https://github.com/lochie/torph/issues\"\n },\n \"files\": [\n \"dist/*\"\n ],\n \"peerDependencies\": {\n \"react\": \">=18\",\n \"react-dom\": \">=18\",\n \"vue\": \">=3\",\n \"svelte\": \">=4\"\n },\n \"peerDependenciesMeta\": {\n \"react\": {\n \"optional\": true\n },\n \"react-dom\": {\n \"optional\": true\n },\n \"vue\": {\n \"optional\": true\n },\n \"svelte\": {\n \"optional\": true\n }\n },\n \"devDependencies\": {\n \"@types/react\": \"^18.2.15\",\n \"@typescript-eslint/eslint-plugin\": \"^6.8.0\",\n \"@typescript-eslint/parser\": \"^6.8.0\",\n \"eslint\": \"^9.36.0\",\n \"eslint-config-airbnb\": \"^19.0.4\",\n \"eslint-config-airbnb-typescript\": \"^17.1.0\",\n \"eslint-config-prettier\": \"^9.0.0\",\n \"eslint-plugin-import\": \"^2.28.1\",\n \"eslint-plugin-jsx-a11y\": \"^6.7.1\",\n \"eslint-plugin-prettier\": \"^5.0.1\",\n \"eslint-plugin-react\": \"^7.33.2\",\n \"eslint-plugin-react-hooks\": \"^4.6.0\",\n \"prettier\": \"^3.0.3\",\n \"react\": \"^18.2.0\",\n \"react-dom\": \"^18.2.0\",\n \"svelte\": \"^4.0.0\",\n \"tsup\": \"^8.5.0\",\n \"typescript\": \"^5.9.3\",\n \"vue\": \"^3.3.0\"\n }\n}\n"],"mappings":";yCAEE,IAAAA,EAAW","names":["version"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/index.ts","../../src/react/TextMorph.tsx","../../src/lib/text-morph/index.ts"],"sourcesContent":["export { TextMorph, useTextMorph } from \"./TextMorph\";\nexport type { TextMorphProps } from \"./TextMorph\";\n\n","\"use client\";\n\nimport React from \"react\";\nimport { TextMorph as Morph } from \"../lib/text-morph\";\nimport type { TextMorphOptions } from \"../lib/text-morph/types\";\n\nexport type TextMorphProps = Omit<TextMorphOptions, \"element\"> & {\n children: string; //React.ReactNode;\n};\n\nexport const TextMorph = ({ children, ...props }: TextMorphProps) => {\n const { ref, update } = useTextMorph(props);\n\n React.useEffect(() => {\n update(children);\n }, [children, update]);\n\n return <div ref={ref} />;\n};\n\nexport function useTextMorph(props: Omit<TextMorphOptions, \"element\">) {\n const ref = React.useRef<HTMLDivElement | null>(null);\n const morphRef = React.useRef<Morph | null>(null);\n\n React.useEffect(() => {\n if (ref.current) {\n morphRef.current = new Morph({ element: ref.current, ...props });\n }\n\n return () => {\n morphRef.current?.destroy();\n };\n }, []);\n\n const update = (text: string) => {\n morphRef.current?.update(text);\n };\n\n return { ref, update };\n}\n","import type { TextMorphOptions } from \"./types\";\n\nexport { TextMorphOptions } from \"./types\";\n\ntype Block = {\n id: string;\n string: string;\n};\ntype Measures = {\n [key: string]: { x: number; y: number };\n};\n\nexport class TextMorph {\n private element: HTMLElement;\n private options: Omit<TextMorphOptions, \"element\"> = {};\n\n private data: HTMLElement | string;\n\n private currentMeasures: Measures = {};\n private prevMeasures: Measures = {};\n private isInitialRender = true;\n\n static styleEl: HTMLStyleElement;\n\n constructor(options: TextMorphOptions) {\n this.options = {\n locale: \"en\",\n duration: 400,\n ease: \"cubic-bezier(0.19, 1, 0.22, 1)\",\n ...options,\n };\n\n this.element = options.element;\n this.element.setAttribute(\"torph-root\", \"\");\n this.element.style.transitionDuration = `${this.options.duration}ms`;\n this.element.style.transitionTimingFunction = this.options.ease!;\n\n if (options.debug) this.element.setAttribute(\"torph-debug\", \"\");\n\n this.data = this.element.innerHTML;\n\n this.addStyles();\n }\n\n destroy() {\n this.element.getAnimations().forEach((anim) => anim.cancel());\n this.element.removeAttribute(\"torph-root\");\n this.element.removeAttribute(\"torph-debug\");\n this.removeStyles();\n }\n\n update(value: HTMLElement | string) {\n if (value === this.data) return;\n this.data = value;\n\n if (this.data instanceof HTMLElement) {\n // TODO: handle HTMLElement case\n throw new Error(\"HTMLElement not yet supported\");\n } else {\n this.createTextGroup(this.data, this.element);\n }\n }\n\n private createTextGroup(value: string, element: HTMLElement) {\n const oldWidth = element.offsetWidth;\n const oldHeight = element.offsetHeight;\n\n const byWord = value.includes(\" \");\n const segmenter = new Intl.Segmenter(this.options.locale, {\n granularity: byWord ? \"word\" : \"grapheme\",\n });\n const iterator = segmenter.segment(value)[Symbol.iterator]();\n const blocks = this.blocks(iterator);\n\n this.prevMeasures = this.measure();\n const oldChildren = Array.from(element.children) as HTMLElement[];\n const newIds = new Set(blocks.map((b) => b.id));\n\n const exiting = oldChildren.filter(\n (child) =>\n !newIds.has(child.getAttribute(\"torph-id\") as string) &&\n !child.hasAttribute(\"torph-exiting\"),\n );\n\n const parentRect = this.getUnscaledBoundingClientRect(element);\n exiting.forEach((child) => {\n const rect = this.getUnscaledBoundingClientRect(child);\n child.setAttribute(\"torph-exiting\", \"\");\n child.style.position = \"absolute\";\n child.style.pointerEvents = \"none\";\n child.style.left = `${rect.left - parentRect.left}px`;\n child.style.top = `${rect.top - parentRect.top}px`;\n child.style.width = `${rect.width}px`;\n child.style.height = `${rect.height}px`;\n });\n\n oldChildren.forEach((child) => {\n const id = child.getAttribute(\"torph-id\") as string;\n if (newIds.has(id)) child.remove();\n });\n\n blocks.forEach((block) => {\n const span = document.createElement(\"span\");\n span.setAttribute(\"torph-item\", \"\");\n span.setAttribute(\"torph-id\", block.id);\n span.textContent = block.string;\n element.appendChild(span);\n });\n\n this.currentMeasures = this.measure();\n this.updateStyles();\n\n exiting.forEach((child) => {\n if (this.isInitialRender) {\n child.remove();\n return;\n }\n\n const id = child.getAttribute(\"torph-id\")!;\n\n const prev = this.prevMeasures[id];\n\n const siblings = Array.from(element.children) as HTMLElement[];\n const nearest = siblings.find((s) => {\n const sRect = s.getBoundingClientRect();\n const cRect = child.getBoundingClientRect();\n return Math.abs(sRect.left - cRect.left) < 40;\n });\n\n const nextPos = nearest\n ? this.currentMeasures[nearest.getAttribute(\"torph-id\")!]\n : prev;\n\n const dx = (nextPos ? nextPos.x - (prev?.x || 0) : 0) * 0.5;\n const dy = (nextPos ? nextPos.y - (prev?.y || 0) : 0) * 0.5;\n\n child.getAnimations().forEach((a) => a.cancel());\n const animation: Animation = child.animate(\n {\n transform: `translate(${dx}px, ${dy}px) scale(0.95)`,\n opacity: 0,\n offset: 1,\n },\n {\n duration: this.options.duration,\n easing: this.options.ease,\n fill: \"both\",\n },\n );\n animation.onfinish = () => child.remove();\n });\n\n if (this.isInitialRender) {\n this.isInitialRender = false;\n element.style.width = \"auto\";\n element.style.height = \"auto\";\n return;\n }\n\n if (oldWidth === 0 || oldHeight === 0) return;\n\n element.style.width = \"auto\";\n element.style.height = \"auto\";\n void element.offsetWidth; // force reflow\n\n const newWidth = element.offsetWidth;\n const newHeight = element.offsetHeight;\n\n element.style.width = `${oldWidth}px`;\n element.style.height = `${oldHeight}px`;\n void element.offsetWidth; // force reflow\n\n element.style.width = `${newWidth}px`;\n element.style.height = `${newHeight}px`;\n\n // TODO: move to `transitionend` event listener\n setTimeout(() => {\n element.style.width = \"auto\";\n element.style.height = \"auto\";\n }, this.options.duration);\n }\n\n private measure() {\n const children = Array.from(this.element.children) as HTMLElement[];\n const measures: Measures = {};\n\n children.forEach((child, index) => {\n if (child.hasAttribute(\"torph-exiting\")) return;\n const key = child.getAttribute(\"torph-id\") || `child-${index}`;\n measures[key] = {\n x: child.offsetLeft,\n y: child.offsetTop,\n };\n });\n\n return measures;\n }\n\n private updateStyles() {\n if (this.isInitialRender) return;\n\n const children = Array.from(this.element.children) as HTMLElement[];\n\n children.forEach((child, index) => {\n if (child.hasAttribute(\"torph-exiting\")) return;\n const key = child.getAttribute(\"torph-id\") || `child-${index}`;\n const prev = this.prevMeasures[key];\n const current = this.currentMeasures[key];\n\n const cx = current?.x || 0;\n const cy = current?.y || 0;\n\n const deltaX = prev ? prev?.x - cx : 0;\n const deltaY = prev ? prev?.y - cy : 0;\n const isNew = !prev;\n\n child.getAnimations().forEach((a) => a.cancel());\n child.animate(\n {\n transform: `translate(${deltaX}px, ${deltaY}px) scale(${isNew ? 0.95 : 1})`,\n opacity: isNew ? 0 : 1,\n offset: 0,\n },\n {\n duration: this.options.duration,\n easing: this.options.ease,\n delay: isNew ? this.options.duration! * 0.2 : 0,\n fill: \"both\",\n },\n );\n });\n }\n\n private addStyles() {\n if (TextMorph.styleEl) return;\n\n const style = document.createElement(\"style\");\n style.dataset.torph = \"true\";\n style.innerHTML = `\n[torph-root] {\n display: inline-flex; /* TODO: remove for multi-line support */\n position: relative;\n will-change: width, height;\n transition-property: width, height;\n}\n\n[torph-item] {\n display: inline-block;\n will-change: opacity, transform;\n transform: none;\n opacity: 1;\n}\n \n[torph-root][torph-debug] {\n outline:2px solid magenta;\n [torph-item] {\n outline:2px solid cyan;\n outline-offset: -4px;\n }\n}\n `;\n document.head.appendChild(style);\n TextMorph.styleEl = style;\n }\n\n private removeStyles() {\n if (TextMorph.styleEl) {\n TextMorph.styleEl.remove();\n TextMorph.styleEl = undefined!;\n }\n }\n\n // utils\n\n private blocks(iterator: Intl.SegmentIterator<Intl.SegmentData>) {\n const uniqueStrings: Block[] = Array.from(iterator).reduce(\n (acc, string) => {\n if (string.segment === \" \") {\n return [...acc, { id: `space-${string.index}`, string: \"\\u00A0\" }];\n }\n\n const existingString = acc.find((x) => x.string === string.segment);\n if (existingString) {\n return [\n ...acc,\n { id: `${string.segment}-${string.index}`, string: string.segment },\n ];\n }\n\n return [\n ...acc,\n {\n id: string.segment,\n string: string.segment,\n },\n ];\n },\n [] as Block[],\n );\n\n return uniqueStrings;\n }\n\n private getUnscaledBoundingClientRect(element: HTMLElement) {\n const scaledRect = element.getBoundingClientRect();\n const computedStyle = window.getComputedStyle(element);\n const transform = computedStyle.transform;\n\n let scaleX = 1;\n let scaleY = 1;\n\n const matrixRegex = /matrix\\(([^)]+)\\)/;\n const match = transform.match(matrixRegex);\n\n if (match) {\n const values = match[1]?.split(\",\").map(Number);\n if (values && values?.length >= 4) {\n scaleX = values[0]!;\n scaleY = values[3]!;\n }\n } else {\n const scaleXMatch = transform.match(/scaleX\\(([^)]+)\\)/);\n const scaleYMatch = transform.match(/scaleY\\(([^)]+)\\)/);\n if (scaleXMatch) scaleX = parseFloat(scaleXMatch[1]!);\n if (scaleYMatch) scaleY = parseFloat(scaleYMatch[1]!);\n }\n\n const unscaledWidth = scaledRect.width / scaleX;\n const unscaledHeight = scaledRect.height / scaleY;\n\n const unscaledX = scaledRect.x + (scaledRect.width - unscaledWidth) / 2;\n const unscaledY = scaledRect.y + (scaledRect.height - unscaledHeight) / 2;\n\n return {\n x: unscaledX,\n y: unscaledY,\n width: unscaledWidth,\n height: unscaledHeight,\n top: unscaledY,\n right: unscaledX + unscaledWidth,\n bottom: unscaledY + unscaledHeight,\n left: unscaledX,\n };\n }\n\n private log(...args: any[]) {\n if (this.options.debug) console.log(\"[TextMorph]\", ...args);\n }\n}\n"],"mappings":"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","names":["react_exports","__export","TextMorph","useTextMorph","__toCommonJS","import_react","TextMorph","_TextMorph","options","anim","value","element","oldWidth","oldHeight","byWord","iterator","blocks","oldChildren","newIds","b","exiting","child","parentRect","rect","id","block","span","prev","nearest","s","sRect","cRect","nextPos","dx","dy","a","animation","newWidth","newHeight","children","measures","index","key","current","cx","cy","deltaX","deltaY","isNew","style","acc","string","x","scaledRect","transform","scaleX","scaleY","matrixRegex","match","values","scaleXMatch","scaleYMatch","unscaledWidth","unscaledHeight","unscaledX","unscaledY","args","TextMorph","children","props","ref","update","useTextMorph","React","morphRef","text"]}
1
+ {"version":3,"sources":["../../src/react/index.ts","../../src/react/TextMorph.tsx","../../src/lib/text-morph/index.ts"],"sourcesContent":["export { TextMorph, useTextMorph } from \"./TextMorph\";\nexport type { TextMorphProps } from \"./TextMorph\";\n\n","\"use client\";\n\nimport React from \"react\";\nimport { TextMorph as Morph } from \"../lib/text-morph\";\nimport type { TextMorphOptions } from \"../lib/text-morph/types\";\n\nexport type TextMorphProps = Omit<TextMorphOptions, \"element\"> & {\n children: string; //React.ReactNode;\n};\n\nexport const TextMorph = ({ children, ...props }: TextMorphProps) => {\n const { ref, update } = useTextMorph(props);\n\n React.useEffect(() => {\n update(children);\n }, [children, update]);\n\n return <div ref={ref} />;\n};\n\nexport function useTextMorph(props: Omit<TextMorphOptions, \"element\">) {\n const ref = React.useRef<HTMLDivElement | null>(null);\n const morphRef = React.useRef<Morph | null>(null);\n\n React.useEffect(() => {\n if (ref.current) {\n morphRef.current = new Morph({ element: ref.current, ...props });\n }\n\n return () => {\n morphRef.current?.destroy();\n };\n }, []);\n\n const update = (text: string) => {\n morphRef.current?.update(text);\n };\n\n return { ref, update };\n}\n","import type { TextMorphOptions } from \"./types\";\n\nexport type { TextMorphOptions } from \"./types\";\n\ntype Block = {\n id: string;\n string: string;\n};\ntype Measures = {\n [key: string]: { x: number; y: number };\n};\n\nexport class TextMorph {\n private element: HTMLElement;\n private options: Omit<TextMorphOptions, \"element\"> = {};\n\n private data: HTMLElement | string;\n\n private currentMeasures: Measures = {};\n private prevMeasures: Measures = {};\n private isInitialRender = true;\n\n static styleEl: HTMLStyleElement;\n\n constructor(options: TextMorphOptions) {\n this.options = {\n locale: \"en\",\n duration: 400,\n ease: \"cubic-bezier(0.19, 1, 0.22, 1)\",\n ...options,\n };\n\n this.element = options.element;\n this.element.setAttribute(\"torph-root\", \"\");\n this.element.style.transitionDuration = `${this.options.duration}ms`;\n this.element.style.transitionTimingFunction = this.options.ease!;\n\n if (options.debug) this.element.setAttribute(\"torph-debug\", \"\");\n\n this.data = this.element.innerHTML;\n\n this.addStyles();\n }\n\n destroy() {\n this.element.getAnimations().forEach((anim) => anim.cancel());\n this.element.removeAttribute(\"torph-root\");\n this.element.removeAttribute(\"torph-debug\");\n this.removeStyles();\n }\n\n update(value: HTMLElement | string) {\n if (value === this.data) return;\n this.data = value;\n\n if (this.data instanceof HTMLElement) {\n // TODO: handle HTMLElement case\n throw new Error(\"HTMLElement not yet supported\");\n } else {\n this.createTextGroup(this.data, this.element);\n }\n }\n\n private createTextGroup(value: string, element: HTMLElement) {\n const oldWidth = element.offsetWidth;\n const oldHeight = element.offsetHeight;\n\n const byWord = value.includes(\" \");\n const segmenter = new Intl.Segmenter(this.options.locale, {\n granularity: byWord ? \"word\" : \"grapheme\",\n });\n const iterator = segmenter.segment(value)[Symbol.iterator]();\n const blocks = this.blocks(iterator);\n\n this.prevMeasures = this.measure();\n const oldChildren = Array.from(element.children) as HTMLElement[];\n const newIds = new Set(blocks.map((b) => b.id));\n\n const exiting = oldChildren.filter(\n (child) =>\n !newIds.has(child.getAttribute(\"torph-id\") as string) &&\n !child.hasAttribute(\"torph-exiting\"),\n );\n\n const parentRect = this.getUnscaledBoundingClientRect(element);\n exiting.forEach((child) => {\n const rect = this.getUnscaledBoundingClientRect(child);\n child.setAttribute(\"torph-exiting\", \"\");\n child.style.position = \"absolute\";\n child.style.pointerEvents = \"none\";\n child.style.left = `${rect.left - parentRect.left}px`;\n child.style.top = `${rect.top - parentRect.top}px`;\n child.style.width = `${rect.width}px`;\n child.style.height = `${rect.height}px`;\n });\n\n oldChildren.forEach((child) => {\n const id = child.getAttribute(\"torph-id\") as string;\n if (newIds.has(id)) child.remove();\n });\n\n blocks.forEach((block) => {\n const span = document.createElement(\"span\");\n span.setAttribute(\"torph-item\", \"\");\n span.setAttribute(\"torph-id\", block.id);\n span.textContent = block.string;\n element.appendChild(span);\n });\n\n this.currentMeasures = this.measure();\n this.updateStyles();\n\n exiting.forEach((child) => {\n if (this.isInitialRender) {\n child.remove();\n return;\n }\n\n const id = child.getAttribute(\"torph-id\")!;\n\n const prev = this.prevMeasures[id];\n\n const siblings = Array.from(element.children) as HTMLElement[];\n const nearest = siblings.find((s) => {\n const sRect = s.getBoundingClientRect();\n const cRect = child.getBoundingClientRect();\n return Math.abs(sRect.left - cRect.left) < 40;\n });\n\n const nextPos = nearest\n ? this.currentMeasures[nearest.getAttribute(\"torph-id\")!]\n : prev;\n\n const dx = (nextPos ? nextPos.x - (prev?.x || 0) : 0) * 0.5;\n const dy = (nextPos ? nextPos.y - (prev?.y || 0) : 0) * 0.5;\n\n child.getAnimations().forEach((a) => a.cancel());\n const animation: Animation = child.animate(\n {\n transform: `translate(${dx}px, ${dy}px) scale(0.95)`,\n opacity: 0,\n offset: 1,\n },\n {\n duration: this.options.duration,\n easing: this.options.ease,\n fill: \"both\",\n },\n );\n animation.onfinish = () => child.remove();\n });\n\n if (this.isInitialRender) {\n this.isInitialRender = false;\n element.style.width = \"auto\";\n element.style.height = \"auto\";\n return;\n }\n\n if (oldWidth === 0 || oldHeight === 0) return;\n\n element.style.width = \"auto\";\n element.style.height = \"auto\";\n void element.offsetWidth; // force reflow\n\n const newWidth = element.offsetWidth;\n const newHeight = element.offsetHeight;\n\n element.style.width = `${oldWidth}px`;\n element.style.height = `${oldHeight}px`;\n void element.offsetWidth; // force reflow\n\n element.style.width = `${newWidth}px`;\n element.style.height = `${newHeight}px`;\n\n // TODO: move to `transitionend` event listener\n setTimeout(() => {\n element.style.width = \"auto\";\n element.style.height = \"auto\";\n }, this.options.duration);\n }\n\n private measure() {\n const children = Array.from(this.element.children) as HTMLElement[];\n const measures: Measures = {};\n\n children.forEach((child, index) => {\n if (child.hasAttribute(\"torph-exiting\")) return;\n const key = child.getAttribute(\"torph-id\") || `child-${index}`;\n measures[key] = {\n x: child.offsetLeft,\n y: child.offsetTop,\n };\n });\n\n return measures;\n }\n\n private updateStyles() {\n if (this.isInitialRender) return;\n\n const children = Array.from(this.element.children) as HTMLElement[];\n\n children.forEach((child, index) => {\n if (child.hasAttribute(\"torph-exiting\")) return;\n const key = child.getAttribute(\"torph-id\") || `child-${index}`;\n const prev = this.prevMeasures[key];\n const current = this.currentMeasures[key];\n\n const cx = current?.x || 0;\n const cy = current?.y || 0;\n\n const deltaX = prev ? prev?.x - cx : 0;\n const deltaY = prev ? prev?.y - cy : 0;\n const isNew = !prev;\n\n child.getAnimations().forEach((a) => a.cancel());\n child.animate(\n {\n transform: `translate(${deltaX}px, ${deltaY}px) scale(${isNew ? 0.95 : 1})`,\n opacity: isNew ? 0 : 1,\n offset: 0,\n },\n {\n duration: this.options.duration,\n easing: this.options.ease,\n delay: isNew ? this.options.duration! * 0.2 : 0,\n fill: \"both\",\n },\n );\n });\n }\n\n private addStyles() {\n if (TextMorph.styleEl) return;\n\n const style = document.createElement(\"style\");\n style.dataset.torph = \"true\";\n style.innerHTML = `\n[torph-root] {\n display: inline-flex; /* TODO: remove for multi-line support */\n position: relative;\n will-change: width, height;\n transition-property: width, height;\n}\n\n[torph-item] {\n display: inline-block;\n will-change: opacity, transform;\n transform: none;\n opacity: 1;\n}\n \n[torph-root][torph-debug] {\n outline:2px solid magenta;\n [torph-item] {\n outline:2px solid cyan;\n outline-offset: -4px;\n }\n}\n `;\n document.head.appendChild(style);\n TextMorph.styleEl = style;\n }\n\n private removeStyles() {\n if (TextMorph.styleEl) {\n TextMorph.styleEl.remove();\n TextMorph.styleEl = undefined!;\n }\n }\n\n // utils\n\n private blocks(iterator: Intl.SegmentIterator<Intl.SegmentData>) {\n const uniqueStrings: Block[] = Array.from(iterator).reduce(\n (acc, string) => {\n if (string.segment === \" \") {\n return [...acc, { id: `space-${string.index}`, string: \"\\u00A0\" }];\n }\n\n const existingString = acc.find((x) => x.string === string.segment);\n if (existingString) {\n return [\n ...acc,\n { id: `${string.segment}-${string.index}`, string: string.segment },\n ];\n }\n\n return [\n ...acc,\n {\n id: string.segment,\n string: string.segment,\n },\n ];\n },\n [] as Block[],\n );\n\n return uniqueStrings;\n }\n\n private getUnscaledBoundingClientRect(element: HTMLElement) {\n const scaledRect = element.getBoundingClientRect();\n const computedStyle = window.getComputedStyle(element);\n const transform = computedStyle.transform;\n\n let scaleX = 1;\n let scaleY = 1;\n\n const matrixRegex = /matrix\\(([^)]+)\\)/;\n const match = transform.match(matrixRegex);\n\n if (match) {\n const values = match[1]?.split(\",\").map(Number);\n if (values && values?.length >= 4) {\n scaleX = values[0]!;\n scaleY = values[3]!;\n }\n } else {\n const scaleXMatch = transform.match(/scaleX\\(([^)]+)\\)/);\n const scaleYMatch = transform.match(/scaleY\\(([^)]+)\\)/);\n if (scaleXMatch) scaleX = parseFloat(scaleXMatch[1]!);\n if (scaleYMatch) scaleY = parseFloat(scaleYMatch[1]!);\n }\n\n const unscaledWidth = scaledRect.width / scaleX;\n const unscaledHeight = scaledRect.height / scaleY;\n\n const unscaledX = scaledRect.x + (scaledRect.width - unscaledWidth) / 2;\n const unscaledY = scaledRect.y + (scaledRect.height - unscaledHeight) / 2;\n\n return {\n x: unscaledX,\n y: unscaledY,\n width: unscaledWidth,\n height: unscaledHeight,\n top: unscaledY,\n right: unscaledX + unscaledWidth,\n bottom: unscaledY + unscaledHeight,\n left: unscaledX,\n };\n }\n\n private log(...args: any[]) {\n if (this.options.debug) console.log(\"[TextMorph]\", ...args);\n }\n}\n"],"mappings":";0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,eAAAE,EAAA,iBAAAC,IAAA,eAAAC,EAAAJ,GCEA,IAAAK,EAAkB,oBCUX,IAAMC,EAAN,MAAMC,CAAU,CACb,QACA,QAA6C,CAAC,EAE9C,KAEA,gBAA4B,CAAC,EAC7B,aAAyB,CAAC,EAC1B,gBAAkB,GAE1B,OAAO,QAEP,YAAYC,EAA2B,CACrC,KAAK,QAAU,CACb,OAAQ,KACR,SAAU,IACV,KAAM,iCACN,GAAGA,CACL,EAEA,KAAK,QAAUA,EAAQ,QACvB,KAAK,QAAQ,aAAa,aAAc,EAAE,EAC1C,KAAK,QAAQ,MAAM,mBAAqB,GAAG,KAAK,QAAQ,QAAQ,KAChE,KAAK,QAAQ,MAAM,yBAA2B,KAAK,QAAQ,KAEvDA,EAAQ,OAAO,KAAK,QAAQ,aAAa,cAAe,EAAE,EAE9D,KAAK,KAAO,KAAK,QAAQ,UAEzB,KAAK,UAAU,CACjB,CAEA,SAAU,CACR,KAAK,QAAQ,cAAc,EAAE,QAASC,GAASA,EAAK,OAAO,CAAC,EAC5D,KAAK,QAAQ,gBAAgB,YAAY,EACzC,KAAK,QAAQ,gBAAgB,aAAa,EAC1C,KAAK,aAAa,CACpB,CAEA,OAAOC,EAA6B,CAClC,GAAIA,IAAU,KAAK,KAGnB,IAFA,KAAK,KAAOA,EAER,KAAK,gBAAgB,YAEvB,MAAM,IAAI,MAAM,+BAA+B,EAE/C,KAAK,gBAAgB,KAAK,KAAM,KAAK,OAAO,EAEhD,CAEQ,gBAAgBA,EAAeC,EAAsB,CAC3D,IAAMC,EAAWD,EAAQ,YACnBE,EAAYF,EAAQ,aAEpBG,EAASJ,EAAM,SAAS,GAAG,EAI3BK,EAHY,IAAI,KAAK,UAAU,KAAK,QAAQ,OAAQ,CACxD,YAAaD,EAAS,OAAS,UACjC,CAAC,EAC0B,QAAQJ,CAAK,EAAE,OAAO,QAAQ,EAAE,EACrDM,EAAS,KAAK,OAAOD,CAAQ,EAEnC,KAAK,aAAe,KAAK,QAAQ,EACjC,IAAME,EAAc,MAAM,KAAKN,EAAQ,QAAQ,EACzCO,EAAS,IAAI,IAAIF,EAAO,IAAKG,GAAMA,EAAE,EAAE,CAAC,EAExCC,EAAUH,EAAY,OACzBI,GACC,CAACH,EAAO,IAAIG,EAAM,aAAa,UAAU,CAAW,GACpD,CAACA,EAAM,aAAa,eAAe,CACvC,EAEMC,EAAa,KAAK,8BAA8BX,CAAO,EAoE7D,GAnEAS,EAAQ,QAASC,GAAU,CACzB,IAAME,EAAO,KAAK,8BAA8BF,CAAK,EACrDA,EAAM,aAAa,gBAAiB,EAAE,EACtCA,EAAM,MAAM,SAAW,WACvBA,EAAM,MAAM,cAAgB,OAC5BA,EAAM,MAAM,KAAO,GAAGE,EAAK,KAAOD,EAAW,IAAI,KACjDD,EAAM,MAAM,IAAM,GAAGE,EAAK,IAAMD,EAAW,GAAG,KAC9CD,EAAM,MAAM,MAAQ,GAAGE,EAAK,KAAK,KACjCF,EAAM,MAAM,OAAS,GAAGE,EAAK,MAAM,IACrC,CAAC,EAEDN,EAAY,QAASI,GAAU,CAC7B,IAAMG,EAAKH,EAAM,aAAa,UAAU,EACpCH,EAAO,IAAIM,CAAE,GAAGH,EAAM,OAAO,CACnC,CAAC,EAEDL,EAAO,QAASS,GAAU,CACxB,IAAMC,EAAO,SAAS,cAAc,MAAM,EAC1CA,EAAK,aAAa,aAAc,EAAE,EAClCA,EAAK,aAAa,WAAYD,EAAM,EAAE,EACtCC,EAAK,YAAcD,EAAM,OACzBd,EAAQ,YAAYe,CAAI,CAC1B,CAAC,EAED,KAAK,gBAAkB,KAAK,QAAQ,EACpC,KAAK,aAAa,EAElBN,EAAQ,QAASC,GAAU,CACzB,GAAI,KAAK,gBAAiB,CACxBA,EAAM,OAAO,EACb,MACF,CAEA,IAAMG,EAAKH,EAAM,aAAa,UAAU,EAElCM,EAAO,KAAK,aAAaH,CAAE,EAG3BI,EADW,MAAM,KAAKjB,EAAQ,QAAQ,EACnB,KAAMkB,GAAM,CACnC,IAAMC,EAAQD,EAAE,sBAAsB,EAChCE,EAAQV,EAAM,sBAAsB,EAC1C,OAAO,KAAK,IAAIS,EAAM,KAAOC,EAAM,IAAI,EAAI,EAC7C,CAAC,EAEKC,EAAUJ,EACZ,KAAK,gBAAgBA,EAAQ,aAAa,UAAU,CAAE,EACtDD,EAEEM,GAAMD,EAAUA,EAAQ,GAAKL,GAAM,GAAK,GAAK,GAAK,GAClDO,GAAMF,EAAUA,EAAQ,GAAKL,GAAM,GAAK,GAAK,GAAK,GAExDN,EAAM,cAAc,EAAE,QAASc,GAAMA,EAAE,OAAO,CAAC,EAC/C,IAAMC,EAAuBf,EAAM,QACjC,CACE,UAAW,aAAaY,CAAE,OAAOC,CAAE,kBACnC,QAAS,EACT,OAAQ,CACV,EACA,CACE,SAAU,KAAK,QAAQ,SACvB,OAAQ,KAAK,QAAQ,KACrB,KAAM,MACR,CACF,EACAE,EAAU,SAAW,IAAMf,EAAM,OAAO,CAC1C,CAAC,EAEG,KAAK,gBAAiB,CACxB,KAAK,gBAAkB,GACvBV,EAAQ,MAAM,MAAQ,OACtBA,EAAQ,MAAM,OAAS,OACvB,MACF,CAEA,GAAIC,IAAa,GAAKC,IAAc,EAAG,OAEvCF,EAAQ,MAAM,MAAQ,OACtBA,EAAQ,MAAM,OAAS,OAClBA,EAAQ,YAEb,IAAM0B,EAAW1B,EAAQ,YACnB2B,EAAY3B,EAAQ,aAE1BA,EAAQ,MAAM,MAAQ,GAAGC,CAAQ,KACjCD,EAAQ,MAAM,OAAS,GAAGE,CAAS,KAC9BF,EAAQ,YAEbA,EAAQ,MAAM,MAAQ,GAAG0B,CAAQ,KACjC1B,EAAQ,MAAM,OAAS,GAAG2B,CAAS,KAGnC,WAAW,IAAM,CACf3B,EAAQ,MAAM,MAAQ,OACtBA,EAAQ,MAAM,OAAS,MACzB,EAAG,KAAK,QAAQ,QAAQ,CAC1B,CAEQ,SAAU,CAChB,IAAM4B,EAAW,MAAM,KAAK,KAAK,QAAQ,QAAQ,EAC3CC,EAAqB,CAAC,EAE5B,OAAAD,EAAS,QAAQ,CAAClB,EAAOoB,IAAU,CACjC,GAAIpB,EAAM,aAAa,eAAe,EAAG,OACzC,IAAMqB,EAAMrB,EAAM,aAAa,UAAU,GAAK,SAASoB,CAAK,GAC5DD,EAASE,CAAG,EAAI,CACd,EAAGrB,EAAM,WACT,EAAGA,EAAM,SACX,CACF,CAAC,EAEMmB,CACT,CAEQ,cAAe,CACrB,GAAI,KAAK,gBAAiB,OAET,MAAM,KAAK,KAAK,QAAQ,QAAQ,EAExC,QAAQ,CAACnB,EAAOoB,IAAU,CACjC,GAAIpB,EAAM,aAAa,eAAe,EAAG,OACzC,IAAMqB,EAAMrB,EAAM,aAAa,UAAU,GAAK,SAASoB,CAAK,GACtDd,EAAO,KAAK,aAAae,CAAG,EAC5BC,EAAU,KAAK,gBAAgBD,CAAG,EAElCE,EAAKD,GAAS,GAAK,EACnBE,EAAKF,GAAS,GAAK,EAEnBG,EAASnB,EAAOA,GAAM,EAAIiB,EAAK,EAC/BG,EAASpB,EAAOA,GAAM,EAAIkB,EAAK,EAC/BG,EAAQ,CAACrB,EAEfN,EAAM,cAAc,EAAE,QAASc,GAAMA,EAAE,OAAO,CAAC,EAC/Cd,EAAM,QACJ,CACE,UAAW,aAAayB,CAAM,OAAOC,CAAM,aAAaC,EAAQ,IAAO,CAAC,IACxE,QAASA,EAAQ,EAAI,EACrB,OAAQ,CACV,EACA,CACE,SAAU,KAAK,QAAQ,SACvB,OAAQ,KAAK,QAAQ,KACrB,MAAOA,EAAQ,KAAK,QAAQ,SAAY,GAAM,EAC9C,KAAM,MACR,CACF,CACF,CAAC,CACH,CAEQ,WAAY,CAClB,GAAIzC,EAAU,QAAS,OAEvB,IAAM0C,EAAQ,SAAS,cAAc,OAAO,EAC5CA,EAAM,QAAQ,MAAQ,OACtBA,EAAM,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAuBlB,SAAS,KAAK,YAAYA,CAAK,EAC/B1C,EAAU,QAAU0C,CACtB,CAEQ,cAAe,CACjB1C,EAAU,UACZA,EAAU,QAAQ,OAAO,EACzBA,EAAU,QAAU,OAExB,CAIQ,OAAOQ,EAAkD,CA0B/D,OAzB+B,MAAM,KAAKA,CAAQ,EAAE,OAClD,CAACmC,EAAKC,IACAA,EAAO,UAAY,IACd,CAAC,GAAGD,EAAK,CAAE,GAAI,SAASC,EAAO,KAAK,GAAI,OAAQ,MAAS,CAAC,EAG5CD,EAAI,KAAME,GAAMA,EAAE,SAAWD,EAAO,OAAO,EAEzD,CACL,GAAGD,EACH,CAAE,GAAI,GAAGC,EAAO,OAAO,IAAIA,EAAO,KAAK,GAAI,OAAQA,EAAO,OAAQ,CACpE,EAGK,CACL,GAAGD,EACH,CACE,GAAIC,EAAO,QACX,OAAQA,EAAO,OACjB,CACF,EAEF,CAAC,CACH,CAGF,CAEQ,8BAA8BxC,EAAsB,CAC1D,IAAM0C,EAAa1C,EAAQ,sBAAsB,EAE3C2C,EADgB,OAAO,iBAAiB3C,CAAO,EACrB,UAE5B4C,EAAS,EACTC,EAAS,EAEPC,EAAc,oBACdC,EAAQJ,EAAU,MAAMG,CAAW,EAEzC,GAAIC,EAAO,CACT,IAAMC,EAASD,EAAM,CAAC,GAAG,MAAM,GAAG,EAAE,IAAI,MAAM,EAC1CC,GAAUA,GAAQ,QAAU,IAC9BJ,EAASI,EAAO,CAAC,EACjBH,EAASG,EAAO,CAAC,EAErB,KAAO,CACL,IAAMC,EAAcN,EAAU,MAAM,mBAAmB,EACjDO,EAAcP,EAAU,MAAM,mBAAmB,EACnDM,IAAaL,EAAS,WAAWK,EAAY,CAAC,CAAE,GAChDC,IAAaL,EAAS,WAAWK,EAAY,CAAC,CAAE,EACtD,CAEA,IAAMC,EAAgBT,EAAW,MAAQE,EACnCQ,EAAiBV,EAAW,OAASG,EAErCQ,EAAYX,EAAW,GAAKA,EAAW,MAAQS,GAAiB,EAChEG,EAAYZ,EAAW,GAAKA,EAAW,OAASU,GAAkB,EAExE,MAAO,CACL,EAAGC,EACH,EAAGC,EACH,MAAOH,EACP,OAAQC,EACR,IAAKE,EACL,MAAOD,EAAYF,EACnB,OAAQG,EAAYF,EACpB,KAAMC,CACR,CACF,CAEQ,OAAOE,EAAa,CACtB,KAAK,QAAQ,OAAO,QAAQ,IAAI,cAAe,GAAGA,CAAI,CAC5D,CACF,EDlVO,IAAMC,EAAY,CAAC,CAAE,SAAAC,EAAU,GAAGC,CAAM,IAAsB,CACnE,GAAM,CAAE,IAAAC,EAAK,OAAAC,CAAO,EAAIC,EAAaH,CAAK,EAE1C,SAAAI,QAAM,UAAU,IAAM,CACpBF,EAAOH,CAAQ,CACjB,EAAG,CAACA,EAAUG,CAAM,CAAC,EAEd,EAAAE,QAAA,cAAC,OAAI,IAAKH,EAAK,CACxB,EAEO,SAASE,EAAaH,EAA0C,CACrE,IAAMC,EAAM,EAAAG,QAAM,OAA8B,IAAI,EAC9CC,EAAW,EAAAD,QAAM,OAAqB,IAAI,EAEhD,SAAAA,QAAM,UAAU,KACVH,EAAI,UACNI,EAAS,QAAU,IAAIP,EAAM,CAAE,QAASG,EAAI,QAAS,GAAGD,CAAM,CAAC,GAG1D,IAAM,CACXK,EAAS,SAAS,QAAQ,CAC5B,GACC,CAAC,CAAC,EAME,CAAE,IAAAJ,EAAK,OAJEK,GAAiB,CAC/BD,EAAS,SAAS,OAAOC,CAAI,CAC/B,CAEqB,CACvB","names":["react_exports","__export","TextMorph","useTextMorph","__toCommonJS","import_react","TextMorph","_TextMorph","options","anim","value","element","oldWidth","oldHeight","byWord","iterator","blocks","oldChildren","newIds","b","exiting","child","parentRect","rect","id","block","span","prev","nearest","s","sRect","cRect","nextPos","dx","dy","a","animation","newWidth","newHeight","children","measures","index","key","current","cx","cy","deltaX","deltaY","isNew","style","acc","string","x","scaledRect","transform","scaleX","scaleY","matrixRegex","match","values","scaleXMatch","scaleYMatch","unscaledWidth","unscaledHeight","unscaledX","unscaledY","args","TextMorph","children","props","ref","update","useTextMorph","React","morphRef","text"]}
@@ -1,3 +1,3 @@
1
1
  "use client";
2
- import{a as n}from"../chunk-43OPPES3.mjs";import r from"react";var x=({children:o,...e})=>{let{ref:t,update:p}=u(e);return r.useEffect(()=>{p(o)},[o,p]),r.createElement("div",{ref:t})};function u(o){let e=r.useRef(null),t=r.useRef(null);return r.useEffect(()=>(e.current&&(t.current=new n({element:e.current,...o})),()=>{t.current?.destroy()}),[]),{ref:e,update:s=>{t.current?.update(s)}}}export{x as TextMorph,u as useTextMorph};
2
+ import{a as n}from"../chunk-4ZXQ2BQA.mjs";import r from"react";var x=({children:o,...e})=>{let{ref:t,update:p}=u(e);return r.useEffect(()=>{p(o)},[o,p]),r.createElement("div",{ref:t})};function u(o){let e=r.useRef(null),t=r.useRef(null);return r.useEffect(()=>(e.current&&(t.current=new n({element:e.current,...o})),()=>{t.current?.destroy()}),[]),{ref:e,update:s=>{t.current?.update(s)}}}export{x as TextMorph,u as useTextMorph};
3
3
  //# sourceMappingURL=index.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "torph",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Dependency-free animated text component.",
5
5
  "author": "Lochie Axon",
6
6
  "license": "MIT",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/text-morph/index.ts"],"sourcesContent":["import type { TextMorphOptions } from \"./types\";\n\nexport { TextMorphOptions } from \"./types\";\n\ntype Block = {\n id: string;\n string: string;\n};\ntype Measures = {\n [key: string]: { x: number; y: number };\n};\n\nexport class TextMorph {\n private element: HTMLElement;\n private options: Omit<TextMorphOptions, \"element\"> = {};\n\n private data: HTMLElement | string;\n\n private currentMeasures: Measures = {};\n private prevMeasures: Measures = {};\n private isInitialRender = true;\n\n static styleEl: HTMLStyleElement;\n\n constructor(options: TextMorphOptions) {\n this.options = {\n locale: \"en\",\n duration: 400,\n ease: \"cubic-bezier(0.19, 1, 0.22, 1)\",\n ...options,\n };\n\n this.element = options.element;\n this.element.setAttribute(\"torph-root\", \"\");\n this.element.style.transitionDuration = `${this.options.duration}ms`;\n this.element.style.transitionTimingFunction = this.options.ease!;\n\n if (options.debug) this.element.setAttribute(\"torph-debug\", \"\");\n\n this.data = this.element.innerHTML;\n\n this.addStyles();\n }\n\n destroy() {\n this.element.getAnimations().forEach((anim) => anim.cancel());\n this.element.removeAttribute(\"torph-root\");\n this.element.removeAttribute(\"torph-debug\");\n this.removeStyles();\n }\n\n update(value: HTMLElement | string) {\n if (value === this.data) return;\n this.data = value;\n\n if (this.data instanceof HTMLElement) {\n // TODO: handle HTMLElement case\n throw new Error(\"HTMLElement not yet supported\");\n } else {\n this.createTextGroup(this.data, this.element);\n }\n }\n\n private createTextGroup(value: string, element: HTMLElement) {\n const oldWidth = element.offsetWidth;\n const oldHeight = element.offsetHeight;\n\n const byWord = value.includes(\" \");\n const segmenter = new Intl.Segmenter(this.options.locale, {\n granularity: byWord ? \"word\" : \"grapheme\",\n });\n const iterator = segmenter.segment(value)[Symbol.iterator]();\n const blocks = this.blocks(iterator);\n\n this.prevMeasures = this.measure();\n const oldChildren = Array.from(element.children) as HTMLElement[];\n const newIds = new Set(blocks.map((b) => b.id));\n\n const exiting = oldChildren.filter(\n (child) =>\n !newIds.has(child.getAttribute(\"torph-id\") as string) &&\n !child.hasAttribute(\"torph-exiting\"),\n );\n\n const parentRect = this.getUnscaledBoundingClientRect(element);\n exiting.forEach((child) => {\n const rect = this.getUnscaledBoundingClientRect(child);\n child.setAttribute(\"torph-exiting\", \"\");\n child.style.position = \"absolute\";\n child.style.pointerEvents = \"none\";\n child.style.left = `${rect.left - parentRect.left}px`;\n child.style.top = `${rect.top - parentRect.top}px`;\n child.style.width = `${rect.width}px`;\n child.style.height = `${rect.height}px`;\n });\n\n oldChildren.forEach((child) => {\n const id = child.getAttribute(\"torph-id\") as string;\n if (newIds.has(id)) child.remove();\n });\n\n blocks.forEach((block) => {\n const span = document.createElement(\"span\");\n span.setAttribute(\"torph-item\", \"\");\n span.setAttribute(\"torph-id\", block.id);\n span.textContent = block.string;\n element.appendChild(span);\n });\n\n this.currentMeasures = this.measure();\n this.updateStyles();\n\n exiting.forEach((child) => {\n if (this.isInitialRender) {\n child.remove();\n return;\n }\n\n const id = child.getAttribute(\"torph-id\")!;\n\n const prev = this.prevMeasures[id];\n\n const siblings = Array.from(element.children) as HTMLElement[];\n const nearest = siblings.find((s) => {\n const sRect = s.getBoundingClientRect();\n const cRect = child.getBoundingClientRect();\n return Math.abs(sRect.left - cRect.left) < 40;\n });\n\n const nextPos = nearest\n ? this.currentMeasures[nearest.getAttribute(\"torph-id\")!]\n : prev;\n\n const dx = (nextPos ? nextPos.x - (prev?.x || 0) : 0) * 0.5;\n const dy = (nextPos ? nextPos.y - (prev?.y || 0) : 0) * 0.5;\n\n child.getAnimations().forEach((a) => a.cancel());\n const animation: Animation = child.animate(\n {\n transform: `translate(${dx}px, ${dy}px) scale(0.95)`,\n opacity: 0,\n offset: 1,\n },\n {\n duration: this.options.duration,\n easing: this.options.ease,\n fill: \"both\",\n },\n );\n animation.onfinish = () => child.remove();\n });\n\n if (this.isInitialRender) {\n this.isInitialRender = false;\n element.style.width = \"auto\";\n element.style.height = \"auto\";\n return;\n }\n\n if (oldWidth === 0 || oldHeight === 0) return;\n\n element.style.width = \"auto\";\n element.style.height = \"auto\";\n void element.offsetWidth; // force reflow\n\n const newWidth = element.offsetWidth;\n const newHeight = element.offsetHeight;\n\n element.style.width = `${oldWidth}px`;\n element.style.height = `${oldHeight}px`;\n void element.offsetWidth; // force reflow\n\n element.style.width = `${newWidth}px`;\n element.style.height = `${newHeight}px`;\n\n // TODO: move to `transitionend` event listener\n setTimeout(() => {\n element.style.width = \"auto\";\n element.style.height = \"auto\";\n }, this.options.duration);\n }\n\n private measure() {\n const children = Array.from(this.element.children) as HTMLElement[];\n const measures: Measures = {};\n\n children.forEach((child, index) => {\n if (child.hasAttribute(\"torph-exiting\")) return;\n const key = child.getAttribute(\"torph-id\") || `child-${index}`;\n measures[key] = {\n x: child.offsetLeft,\n y: child.offsetTop,\n };\n });\n\n return measures;\n }\n\n private updateStyles() {\n if (this.isInitialRender) return;\n\n const children = Array.from(this.element.children) as HTMLElement[];\n\n children.forEach((child, index) => {\n if (child.hasAttribute(\"torph-exiting\")) return;\n const key = child.getAttribute(\"torph-id\") || `child-${index}`;\n const prev = this.prevMeasures[key];\n const current = this.currentMeasures[key];\n\n const cx = current?.x || 0;\n const cy = current?.y || 0;\n\n const deltaX = prev ? prev?.x - cx : 0;\n const deltaY = prev ? prev?.y - cy : 0;\n const isNew = !prev;\n\n child.getAnimations().forEach((a) => a.cancel());\n child.animate(\n {\n transform: `translate(${deltaX}px, ${deltaY}px) scale(${isNew ? 0.95 : 1})`,\n opacity: isNew ? 0 : 1,\n offset: 0,\n },\n {\n duration: this.options.duration,\n easing: this.options.ease,\n delay: isNew ? this.options.duration! * 0.2 : 0,\n fill: \"both\",\n },\n );\n });\n }\n\n private addStyles() {\n if (TextMorph.styleEl) return;\n\n const style = document.createElement(\"style\");\n style.dataset.torph = \"true\";\n style.innerHTML = `\n[torph-root] {\n display: inline-flex; /* TODO: remove for multi-line support */\n position: relative;\n will-change: width, height;\n transition-property: width, height;\n}\n\n[torph-item] {\n display: inline-block;\n will-change: opacity, transform;\n transform: none;\n opacity: 1;\n}\n \n[torph-root][torph-debug] {\n outline:2px solid magenta;\n [torph-item] {\n outline:2px solid cyan;\n outline-offset: -4px;\n }\n}\n `;\n document.head.appendChild(style);\n TextMorph.styleEl = style;\n }\n\n private removeStyles() {\n if (TextMorph.styleEl) {\n TextMorph.styleEl.remove();\n TextMorph.styleEl = undefined!;\n }\n }\n\n // utils\n\n private blocks(iterator: Intl.SegmentIterator<Intl.SegmentData>) {\n const uniqueStrings: Block[] = Array.from(iterator).reduce(\n (acc, string) => {\n if (string.segment === \" \") {\n return [...acc, { id: `space-${string.index}`, string: \"\\u00A0\" }];\n }\n\n const existingString = acc.find((x) => x.string === string.segment);\n if (existingString) {\n return [\n ...acc,\n { id: `${string.segment}-${string.index}`, string: string.segment },\n ];\n }\n\n return [\n ...acc,\n {\n id: string.segment,\n string: string.segment,\n },\n ];\n },\n [] as Block[],\n );\n\n return uniqueStrings;\n }\n\n private getUnscaledBoundingClientRect(element: HTMLElement) {\n const scaledRect = element.getBoundingClientRect();\n const computedStyle = window.getComputedStyle(element);\n const transform = computedStyle.transform;\n\n let scaleX = 1;\n let scaleY = 1;\n\n const matrixRegex = /matrix\\(([^)]+)\\)/;\n const match = transform.match(matrixRegex);\n\n if (match) {\n const values = match[1]?.split(\",\").map(Number);\n if (values && values?.length >= 4) {\n scaleX = values[0]!;\n scaleY = values[3]!;\n }\n } else {\n const scaleXMatch = transform.match(/scaleX\\(([^)]+)\\)/);\n const scaleYMatch = transform.match(/scaleY\\(([^)]+)\\)/);\n if (scaleXMatch) scaleX = parseFloat(scaleXMatch[1]!);\n if (scaleYMatch) scaleY = parseFloat(scaleYMatch[1]!);\n }\n\n const unscaledWidth = scaledRect.width / scaleX;\n const unscaledHeight = scaledRect.height / scaleY;\n\n const unscaledX = scaledRect.x + (scaledRect.width - unscaledWidth) / 2;\n const unscaledY = scaledRect.y + (scaledRect.height - unscaledHeight) / 2;\n\n return {\n x: unscaledX,\n y: unscaledY,\n width: unscaledWidth,\n height: unscaledHeight,\n top: unscaledY,\n right: unscaledX + unscaledWidth,\n bottom: unscaledY + unscaledHeight,\n left: unscaledX,\n };\n }\n\n private log(...args: any[]) {\n if (this.options.debug) console.log(\"[TextMorph]\", ...args);\n }\n}\n"],"mappings":"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","names":["TextMorph","_TextMorph","options","anim","value","element","oldWidth","oldHeight","byWord","iterator","blocks","oldChildren","newIds","b","exiting","child","parentRect","rect","id","block","span","prev","nearest","s","sRect","cRect","nextPos","dx","dy","a","animation","newWidth","newHeight","children","measures","index","key","current","cx","cy","deltaX","deltaY","isNew","style","acc","string","x","scaledRect","transform","scaleX","scaleY","matrixRegex","match","values","scaleXMatch","scaleYMatch","unscaledWidth","unscaledHeight","unscaledX","unscaledY","args"]}