torph 0.0.1 → 0.0.2

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.
Files changed (77) hide show
  1. package/README.md +114 -12
  2. package/dist/.DS_Store +0 -0
  3. package/dist/TextMorph-TPUHANOZ.vue +47 -0
  4. package/dist/chunk-KHX5JMQ4.mjs +25 -0
  5. package/dist/chunk-KHX5JMQ4.mjs.map +1 -0
  6. package/dist/index.d.mts +23 -6
  7. package/dist/index.d.ts +23 -6
  8. package/dist/index.js +23 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +1 -1
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/react/index.d.mts +13 -0
  13. package/dist/react/index.d.ts +13 -0
  14. package/dist/react/index.js +25 -0
  15. package/dist/react/index.js.map +1 -0
  16. package/dist/react/index.mjs +3 -0
  17. package/dist/react/index.mjs.map +1 -0
  18. package/dist/svelte/index.d.mts +11 -0
  19. package/dist/svelte/index.js +2 -0
  20. package/dist/svelte/index.js.map +1 -0
  21. package/dist/svelte/index.mjs +2 -0
  22. package/dist/svelte/index.mjs.map +1 -0
  23. package/dist/types-CsvRfPun.d.mts +9 -0
  24. package/dist/types-CsvRfPun.d.ts +9 -0
  25. package/dist/vue/index.js +2 -0
  26. package/dist/vue/index.js.map +1 -0
  27. package/dist/vue/index.mjs +2 -0
  28. package/dist/vue/index.mjs.map +1 -0
  29. package/package.json +41 -12
  30. package/dist/chunk-2RJBUJEF.mjs +0 -3
  31. package/dist/chunk-2RJBUJEF.mjs.map +0 -1
  32. package/dist/chunk-66KQLHAM.mjs +0 -3
  33. package/dist/chunk-66KQLHAM.mjs.map +0 -1
  34. package/dist/chunk-I42X5RZV.mjs +0 -3
  35. package/dist/chunk-I42X5RZV.mjs.map +0 -1
  36. package/dist/chunk-RM2STLUA.mjs +0 -2
  37. package/dist/chunk-RM2STLUA.mjs.map +0 -1
  38. package/dist/chunk-SZ3JXWX5.mjs +0 -3
  39. package/dist/chunk-SZ3JXWX5.mjs.map +0 -1
  40. package/dist/chunk-Y6MZSIYO.mjs +0 -3
  41. package/dist/chunk-Y6MZSIYO.mjs.map +0 -1
  42. package/dist/components/text-morph/index.d.mts +0 -7
  43. package/dist/components/text-morph/index.d.ts +0 -7
  44. package/dist/components/text-morph/index.js +0 -3
  45. package/dist/components/text-morph/index.js.map +0 -1
  46. package/dist/components/text-morph/index.mjs +0 -3
  47. package/dist/components/text-morph/index.mjs.map +0 -1
  48. package/dist/components/text-morph/styles.d.mts +0 -5
  49. package/dist/components/text-morph/styles.d.ts +0 -5
  50. package/dist/components/text-morph/styles.js +0 -3
  51. package/dist/components/text-morph/styles.js.map +0 -1
  52. package/dist/components/text-morph/styles.mjs +0 -3
  53. package/dist/components/text-morph/styles.mjs.map +0 -1
  54. package/dist/components/text-morph/types.d.mts +0 -12
  55. package/dist/components/text-morph/types.d.ts +0 -12
  56. package/dist/components/text-morph/types.js +0 -3
  57. package/dist/components/text-morph/types.js.map +0 -1
  58. package/dist/components/text-morph/types.mjs +0 -3
  59. package/dist/components/text-morph/types.mjs.map +0 -1
  60. package/dist/config.d.mts +0 -10
  61. package/dist/config.d.ts +0 -10
  62. package/dist/config.js +0 -3
  63. package/dist/config.js.map +0 -1
  64. package/dist/config.mjs +0 -3
  65. package/dist/config.mjs.map +0 -1
  66. package/dist/hooks/useDebounce.d.mts +0 -3
  67. package/dist/hooks/useDebounce.d.ts +0 -3
  68. package/dist/hooks/useDebounce.js +0 -3
  69. package/dist/hooks/useDebounce.js.map +0 -1
  70. package/dist/hooks/useDebounce.mjs +0 -3
  71. package/dist/hooks/useDebounce.mjs.map +0 -1
  72. package/dist/utils.d.mts +0 -4
  73. package/dist/utils.d.ts +0 -4
  74. package/dist/utils.js +0 -3
  75. package/dist/utils.js.map +0 -1
  76. package/dist/utils.mjs +0 -3
  77. package/dist/utils.mjs.map +0 -1
package/README.md CHANGED
@@ -1,37 +1,139 @@
1
- # torph
1
+ # Torph
2
2
 
3
- An animated text component for React.
3
+ Animated text morphing component for React, Vue, and Svelte.
4
4
 
5
5
  ## Installation
6
6
 
7
- ```shell
8
- pnpm i torph
7
+ ```bash
8
+ npm install torph
9
+ # or
10
+ pnpm add torph
11
+ # or
12
+ yarn add torph
9
13
  ```
10
14
 
11
- ## Usage
15
+ ## Framework Usage
16
+
17
+ ### React
12
18
 
13
19
  ```tsx
14
- import { TextMorph } from "torph";
20
+ import { TextMorph } from 'torph/react';
21
+
22
+ function App() {
23
+ const [text, setText] = useState('Hello World');
15
24
 
16
- <TextMorph>Hello world</TextMorph>;
25
+ return (
26
+ <TextMorph
27
+ duration={400}
28
+ ease="cubic-bezier(0.19, 1, 0.22, 1)"
29
+ locale="en"
30
+ >
31
+ {text}
32
+ </TextMorph>
33
+ );
34
+ }
17
35
  ```
18
36
 
19
- ## Options
37
+ #### React Hook
20
38
 
21
39
  ```tsx
40
+ import { useTextMorph } from 'torph/react';
41
+
42
+ function CustomComponent() {
43
+ const { ref, update } = useTextMorph({
44
+ duration: 400,
45
+ ease: "cubic-bezier(0.19, 1, 0.22, 1)",
46
+ });
47
+
48
+ useEffect(() => {
49
+ update('Hello World');
50
+ }, []);
51
+
52
+ return <div ref={ref} />;
53
+ }
54
+ ```
55
+
56
+ ### Vue
57
+
58
+ ```vue
59
+ <script setup>
60
+ import { ref } from 'vue';
61
+ import { TextMorph } from 'torph/vue';
62
+
63
+ const text = ref('Hello World');
64
+ </script>
65
+
66
+ <template>
67
+ <TextMorph
68
+ :text="text"
69
+ :duration="400"
70
+ ease="cubic-bezier(0.19, 1, 0.22, 1)"
71
+ locale="en"
72
+ />
73
+ </template>
74
+ ```
75
+
76
+ ### Svelte
77
+
78
+ ```svelte
79
+ <script>
80
+ import { TextMorph } from 'torph/svelte';
81
+
82
+ let text = 'Hello World';
83
+ </script>
84
+
22
85
  <TextMorph
23
- children="Hello world"
24
- duration={1} // duration of the transition in seconds
86
+ {text}
87
+ duration={400}
88
+ ease="cubic-bezier(0.19, 1, 0.22, 1)"
89
+ locale="en"
25
90
  />
26
91
  ```
27
92
 
28
- ## Found it useful?
93
+ ### Vanilla JS
94
+
95
+ ```js
96
+ import { TextMorph } from 'torph';
97
+
98
+ const morph = new TextMorph({
99
+ element: document.getElementById('morph'),
100
+ duration: 400,
101
+ ease: 'cubic-bezier(0.19, 1, 0.22, 1)',
102
+ locale: 'en',
103
+ });
104
+
105
+ morph.update('Hello World');
106
+ ```
107
+
108
+ ## API
109
+
110
+ ### Options
111
+
112
+ All components accept the following props/options:
113
+
114
+ - `text` / `children: string` - The text to display (required)
115
+ - `duration?: number` - Animation duration in milliseconds (default: 400)
116
+ - `ease?: string` - CSS easing function (default: "cubic-bezier(0.19, 1, 0.22, 1)")
117
+ - `locale?: Intl.LocalesArgument` - Locale for text segmentation (default: "en")
118
+ - `debug?: boolean` - Enable debug mode with visual indicators
119
+
120
+ ## Found this useful?
29
121
 
30
122
  Follow me on [Twitter](https://twitter.com/lochieaxon).
31
123
 
32
- ### Other projects
124
+ ## Other projects
33
125
 
34
126
  You might also like:
35
127
 
36
128
  - [number-flow](https://number-flow.barvian.me/) - Animated number component by [Maxwell Barvian](https://x.com/mbarvian).
37
129
  - [easing.dev](https://easing.dev) - Easily create custom easing graphs.
130
+
131
+ ## Acknowledgements
132
+
133
+ - Thanks to [Alex](https://x.com/alexvanderzon) for assistance with the site design.
134
+ - Thanks to [Pugson](https://x.com/pugson) for putting up with my bullshit.
135
+ - Thanks to [Benji](https://x.com/benjitaylor) for coining the `Torph` name and outlining the method in [Family Values](https://benji.org/family-values#:~:text=This%20effect%20is,0.5x).
136
+
137
+ ## License
138
+
139
+ MIT
package/dist/.DS_Store CHANGED
Binary file
@@ -0,0 +1,47 @@
1
+ <template>
2
+ <div ref="containerRef"></div>
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ import { ref, onMounted, onUnmounted, watch } from 'vue';
7
+ import { TextMorph as Morph } from '../lib/text-morph';
8
+ import type { TextMorphOptions } from '../lib/text-morph/types';
9
+
10
+ export interface TextMorphProps extends Omit<TextMorphOptions, 'element'> {
11
+ text: string;
12
+ }
13
+
14
+ const props = withDefaults(defineProps<TextMorphProps>(), {
15
+ locale: 'en',
16
+ duration: 400,
17
+ ease: 'cubic-bezier(0.19, 1, 0.22, 1)',
18
+ });
19
+
20
+ const containerRef = ref<HTMLDivElement | null>(null);
21
+ let morphInstance: Morph | null = null;
22
+
23
+ onMounted(() => {
24
+ if (containerRef.value) {
25
+ morphInstance = new Morph({
26
+ element: containerRef.value,
27
+ locale: props.locale,
28
+ duration: props.duration,
29
+ ease: props.ease,
30
+ debug: props.debug,
31
+ });
32
+ morphInstance.update(props.text);
33
+ }
34
+ });
35
+
36
+ onUnmounted(() => {
37
+ morphInstance?.destroy();
38
+ });
39
+
40
+ watch(
41
+ () => props.text,
42
+ (newText) => {
43
+ morphInstance?.update(newText);
44
+ }
45
+ );
46
+ </script>
47
+
@@ -0,0 +1,25 @@
1
+ "use client";
2
+ var E=class g{element;options={};data;currentMeasures={};prevMeasures={};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,o=e.includes(" "),m=new Intl.Segmenter(this.options.locale,{granularity:o?"word":"grapheme"}).segment(e)[Symbol.iterator](),d=this.blocks(m);this.prevMeasures=this.measure();let c=Array.from(t.children),p=new Set(d.map(s=>s.id)),a=c.filter(s=>!p.has(s.getAttribute("torph-id"))&&!s.hasAttribute("torph-exiting")),u=this.getUnscaledBoundingClientRect(t);if(a.forEach(s=>{let r=this.getUnscaledBoundingClientRect(s);s.setAttribute("torph-exiting",""),s.style.position="absolute",s.style.pointerEvents="none",s.style.left=`${r.left-u.left}px`,s.style.top=`${r.top-u.top}px`,s.style.width=`${r.width}px`,s.style.height=`${r.height}px`}),c.forEach(s=>{let r=s.getAttribute("torph-id");p.has(r)&&s.remove()}),d.forEach(s=>{let r=document.createElement("span");r.setAttribute("torph-item",""),r.setAttribute("torph-id",s.id),r.textContent=s.string,t.appendChild(r)}),this.currentMeasures=this.measure(),this.updateStyles(),a.forEach(s=>{let r=s.getAttribute("torph-id"),x=this.prevMeasures[r],M=Array.from(t.children).find(b=>{let T=b.getBoundingClientRect(),H=s.getBoundingClientRect();return Math.abs(T.left-H.left)<40}),y=M?this.currentMeasures[M.getAttribute("torph-id")]:x,v=(y?y.x-(x?.x||0):0)*.5,A=(y?y.y-(x?.y||0):0)*.5;s.getAnimations().forEach(b=>b.cancel());let w=s.animate({transform:`translate(${v}px, ${A}px) scale(0.95)`,opacity:0,offset:1},{duration:this.options.duration,easing:this.options.ease,fill:"both"});w.onfinish=()=>s.remove()}),n===0||i===0)return;t.style.width="auto",t.style.height="auto",t.offsetWidth;let l=t.offsetWidth,f=t.offsetHeight;t.style.width=`${n}px`,t.style.height=`${i}px`,t.offsetWidth,t.style.width=`${l}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 o=n.getAttribute("torph-id")||`child-${i}`;t[o]={x:n.offsetLeft,y:n.offsetTop}}),t}updateStyles(){Array.from(this.element.children).forEach((t,n)=>{if(t.hasAttribute("torph-exiting"))return;let i=t.getAttribute("torph-id")||`child-${n}`,o=this.prevMeasures[i],h=this.currentMeasures[i],m=h?.x||0,d=h?.y||0,c=o?o?.x-m:0,p=o?o?.y-d:0,a=!o;t.getAnimations().forEach(u=>u.cancel()),t.animate({transform:`translate(${c}px, ${p}px) scale(${a?.95:1})`,opacity:a?0:1,offset:0},{duration:this.options.duration,easing:this.options.ease,delay:a?this.options.duration*.2:0,fill:"both"})})}addStyles(){if(g.styleEl)return;let e=document.createElement("style");e.dataset.torph="true",e.innerHTML=`
3
+ [torph-root] {
4
+ display: inline-flex; /* TODO: remove for multi-line support */
5
+ position: relative;
6
+ will-change: width, height;
7
+ transition-property: width, height;
8
+ }
9
+
10
+ [torph-item] {
11
+ display: inline-block;
12
+ will-change: opacity, transform;
13
+ transform: none;
14
+ opacity: 1;
15
+ }
16
+
17
+ [torph-root][torph-debug] {
18
+ outline:2px solid magenta;
19
+ [torph-item] {
20
+ outline:2px solid cyan;
21
+ outline-offset: -4px;
22
+ }
23
+ }
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,o=1,h=1,m=/matrix\(([^)]+)\)/,d=i.match(m);if(d){let l=d[1]?.split(",").map(Number);l&&l?.length>=4&&(o=l[0],h=l[3])}else{let l=i.match(/scaleX\(([^)]+)\)/),f=i.match(/scaleY\(([^)]+)\)/);l&&(o=parseFloat(l[1])),f&&(h=parseFloat(f[1]))}let c=t.width/o,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{E as a};
25
+ //# sourceMappingURL=chunk-KHX5JMQ4.mjs.map
@@ -0,0 +1 @@
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\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 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 (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 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,8 +1,25 @@
1
- export { TextMorph } from './components/text-morph/index.mjs';
2
- export { TextMorphProps } from './components/text-morph/types.mjs';
3
- import 'react/jsx-runtime';
4
- import 'motion';
1
+ import { T as TextMorphOptions } from './types-CsvRfPun.mjs';
5
2
 
6
- var version = "0.0.1";
3
+ var version = "0.0.2";
7
4
 
8
- export { version };
5
+ declare class TextMorph {
6
+ private element;
7
+ private options;
8
+ private data;
9
+ private currentMeasures;
10
+ private prevMeasures;
11
+ static styleEl: HTMLStyleElement;
12
+ constructor(options: TextMorphOptions);
13
+ destroy(): void;
14
+ update(value: HTMLElement | string): void;
15
+ private createTextGroup;
16
+ private measure;
17
+ private updateStyles;
18
+ private addStyles;
19
+ private removeStyles;
20
+ private blocks;
21
+ private getUnscaledBoundingClientRect;
22
+ private log;
23
+ }
24
+
25
+ export { TextMorph, TextMorphOptions, version };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,25 @@
1
- export { TextMorph } from './components/text-morph/index.js';
2
- export { TextMorphProps } from './components/text-morph/types.js';
3
- import 'react/jsx-runtime';
4
- import 'motion';
1
+ import { T as TextMorphOptions } from './types-CsvRfPun.js';
5
2
 
6
- var version = "0.0.1";
3
+ var version = "0.0.2";
7
4
 
8
- export { version };
5
+ declare class TextMorph {
6
+ private element;
7
+ private options;
8
+ private data;
9
+ private currentMeasures;
10
+ private prevMeasures;
11
+ static styleEl: HTMLStyleElement;
12
+ constructor(options: TextMorphOptions);
13
+ destroy(): void;
14
+ update(value: HTMLElement | string): void;
15
+ private createTextGroup;
16
+ private measure;
17
+ private updateStyles;
18
+ private addStyles;
19
+ private removeStyles;
20
+ private blocks;
21
+ private getUnscaledBoundingClientRect;
22
+ private log;
23
+ }
24
+
25
+ export { TextMorph, TextMorphOptions, version };
package/dist/index.js CHANGED
@@ -1,3 +1,25 @@
1
1
  "use client";
2
- "use strict";var q=Object.create;var g=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var V=Object.getPrototypeOf,W=Object.prototype.hasOwnProperty;var H=(t,e)=>{for(var n in e)g(t,n,{get:e[n],enumerable:!0})},C=(t,e,n,c)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of N(e))!W.call(t,i)&&i!==n&&g(t,i,{get:()=>e[i],enumerable:!(c=z(e,i))||c.enumerable});return t};var S=(t,e,n)=>(n=t!=null?q(V(t)):{},C(e||!t||!t.__esModule?g(n,"default",{value:t,enumerable:!0}):n,t)),B=t=>C(g({},"__esModule",{value:!0}),t);var Q={};H(Q,{TextMorph:()=>k,version:()=>D});module.exports=B(Q);var D="0.0.1";var u=S(require("react")),a=require("motion/react");var G={container:{position:"relative",display:"inline-flex"},span:{position:"relative",display:"inline-flex",whiteSpace:"pre"},debug:{outline:"1px solid red"}},l=G;var J={duration:.4,ease:[.19,1,.22,1],respectMotionPreference:!0},b=J;var L=(t,e)=>{let n=t.length,c=e.length,i=Array.from({length:n+1},()=>Array(c+1).fill(0)),s=0,d=0;for(let o=1;o<=n;o+=1)for(let r=1;r<=c;r+=1)t[o-1]===e[r-1]&&(i[o][r]=i[o-1][r-1]+1,i[o][r]>s&&(s=i[o][r],d=o));return t.substring(d-s,d)};var P=S(require("react")),K=(t,e)=>{let[n,c]=P.default.useState(t);return P.default.useEffect(()=>{let i=setTimeout(()=>{c(t)},e);return()=>{clearTimeout(i)}},[t,e]),n},j=K;var p=require("react/jsx-runtime"),k=({debug:t,children:e,duration:n=b.duration,ease:c=b.ease,onAnimationComplete:i})=>{let s=u.default.useId(),d=u.default.useRef(e),o=j(e,100),r=u.default.useRef(null),[w,I]=u.default.useState("auto"),m={duration:n,ease:c},E=d.current;u.default.useEffect(()=>{d.current=o},[o]),u.default.useEffect(()=>{if(r.current){let y=new ResizeObserver(x=>{let h=x[0]?.contentRect.width;h&&I(h)});return y.observe(r.current),()=>{y.disconnect()}}return()=>{}},[]);let f=L(E,e),M=f?e.indexOf(f):0,O=f?M+f.length:0,$=e.slice(0,M),v=e.slice(O),R="0.125em",T=(y,x,h)=>(0,p.jsx)(a.motion.span,{style:l.span,layout:"position",layoutId:`${s}-${x}`,exit:{opacity:0,scale:.95},transition:m,onAnimationComplete:i,children:(0,p.jsx)(a.AnimatePresence,{initial:h!==.5,mode:"popLayout",children:(0,p.jsx)(a.motion.span,{style:l.span,initial:{x:h===0||h===1?R:0,opacity:0,scale:.95},animate:{x:0,opacity:1,scale:1},transition:{...m,delay:(m.duration??b.duration)*.2},children:x},`${s}-${y}-${x}`)})},`${s}-${x}`),A={width:w};return(0,p.jsx)(a.motion.span,{style:{...l.container,...t?l.debug:{}},initial:A,animate:A,transition:m,children:(0,p.jsx)("span",{ref:r,style:l.span,children:(0,p.jsx)(a.AnimatePresence,{initial:!1,mode:"popLayout",children:(0,p.jsx)(a.motion.span,{layoutId:`${s}-torph`,layout:"position",transition:m,style:l.span,children:(0,p.jsxs)(a.AnimatePresence,{initial:!1,mode:"popLayout",children:[$&&T(`${s}-prefix`,$,0),f&&(0,p.jsx)(a.motion.span,{layout:"position",layoutId:`${s}-lcs`,transition:m,style:{...l.span},children:T(`${s}-lcs-chars`,f,.5)}),v&&T(`${s}-suffix`,v,1)]})},`${s}-torph`)})})})};0&&(module.exports={TextMorph,version});
2
+ "use strict";var M=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var j=(o,e)=>{for(var t in e)M(o,t,{get:e[t],enumerable:!0})},R=(o,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of k(e))!C.call(o,s)&&s!==t&&M(o,s,{get:()=>e[s],enumerable:!(n=L(e,s))||n.enumerable});return o};var B=o=>R(M({},"__esModule",{value:!0}),o);var W={};j(W,{TextMorph:()=>x,version:()=>A});module.exports=B(W);var A="0.0.2";var x=class o{element;options={};data;currentMeasures={};prevMeasures={};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,s=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(i=>i.id)),p=h.filter(i=>!d.has(i.getAttribute("torph-id"))&&!i.hasAttribute("torph-exiting")),u=this.getUnscaledBoundingClientRect(t);if(p.forEach(i=>{let a=this.getUnscaledBoundingClientRect(i);i.setAttribute("torph-exiting",""),i.style.position="absolute",i.style.pointerEvents="none",i.style.left=`${a.left-u.left}px`,i.style.top=`${a.top-u.top}px`,i.style.width=`${a.width}px`,i.style.height=`${a.height}px`}),h.forEach(i=>{let a=i.getAttribute("torph-id");d.has(a)&&i.remove()}),m.forEach(i=>{let a=document.createElement("span");a.setAttribute("torph-item",""),a.setAttribute("torph-id",i.id),a.textContent=i.string,t.appendChild(a)}),this.currentMeasures=this.measure(),this.updateStyles(),p.forEach(i=>{let a=i.getAttribute("torph-id"),v=this.prevMeasures[a],E=Array.from(t.children).find(b=>{let S=b.getBoundingClientRect(),$=i.getBoundingClientRect();return Math.abs(S.left-$.left)<40}),f=E?this.currentMeasures[E.getAttribute("torph-id")]:v,T=(f?f.x-(v?.x||0):0)*.5,w=(f?f.y-(v?.y||0):0)*.5;i.getAnimations().forEach(b=>b.cancel());let H=i.animate({transform:`translate(${T}px, ${w}px) scale(0.95)`,opacity:0,offset:1},{duration:this.options.duration,easing:this.options.ease,fill:"both"});H.onfinish=()=>i.remove()}),n===0||s===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=`${s}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,s)=>{if(n.hasAttribute("torph-exiting"))return;let r=n.getAttribute("torph-id")||`child-${s}`;t[r]={x:n.offsetLeft,y:n.offsetTop}}),t}updateStyles(){Array.from(this.element.children).forEach((t,n)=>{if(t.hasAttribute("torph-exiting"))return;let s=t.getAttribute("torph-id")||`child-${n}`,r=this.prevMeasures[s],l=this.currentMeasures[s],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
+ [torph-root] {
4
+ display: inline-flex; /* TODO: remove for multi-line support */
5
+ position: relative;
6
+ will-change: width, height;
7
+ transition-property: width, height;
8
+ }
9
+
10
+ [torph-item] {
11
+ display: inline-block;
12
+ will-change: opacity, transform;
13
+ transform: none;
14
+ opacity: 1;
15
+ }
16
+
17
+ [torph-root][torph-debug] {
18
+ outline:2px solid magenta;
19
+ [torph-item] {
20
+ outline:2px solid cyan;
21
+ outline-offset: -4px;
22
+ }
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,s)=>s.segment===" "?[...n,{id:`space-${s.index}`,string:"\xA0"}]:n.find(l=>l.string===s.segment)?[...n,{id:`${s.segment}-${s.index}`,string:s.segment}]:[...n,{id:s.segment,string:s.segment}],[])}getUnscaledBoundingClientRect(e){let t=e.getBoundingClientRect(),s=window.getComputedStyle(e).transform,r=1,l=1,g=/matrix\(([^)]+)\)/,m=s.match(g);if(m){let c=m[1]?.split(",").map(Number);c&&c?.length>=4&&(r=c[0],l=c[3])}else{let c=s.match(/scaleX\(([^)]+)\)/),y=s.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});
3
25
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../package.json","../src/components/text-morph/index.tsx","../src/components/text-morph/styles.ts","../src/config.ts","../src/utils.ts","../src/hooks/useDebounce.ts"],"sourcesContent":["export { version } from \"./../package.json\";\n\nexport { TextMorph } from \"./components/text-morph\";\nexport { TextMorphProps } from \"./components/text-morph/types\";\n","{\n \"name\": \"torph\",\n \"version\": \"0.0.1\",\n \"description\": \"An animated text component for React.\",\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 \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.js\"\n },\n \"./*\": {\n \"import\": \"./dist/components/*/index.mjs\",\n \"require\": \"./dist/components/*/index.js\",\n \"types\": \"./dist/components/*/index.d.ts\"\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 \"text\",\n \"animation\"\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 },\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 \"tsup\": \"^8.5.0\",\n \"typescript\": \"^5.0.2\"\n },\n \"dependencies\": {\n \"motion\": \"^12.23.16\"\n }\n}\n","\"use client\";\n\nimport React from \"react\";\n\nimport { motion, AnimatePresence, Transition } from \"motion/react\";\nimport styles from \"./styles\";\n\nimport { TextMorphProps } from \"./types\";\nimport defaultConfig from \"../../config\";\nimport { findLCS } from \"../../utils\";\n\nimport useDebounce from \"../../hooks/useDebounce\";\n\nexport const TextMorph = ({\n debug,\n children,\n //fontSize,\n //fontWeight,\n duration = defaultConfig.duration,\n ease = defaultConfig.ease,\n // respectMotionPreference = defaultConfig.respectMotionPreference,\n onAnimationComplete,\n}: TextMorphProps) => {\n const id = React.useId();\n const previousRef = React.useRef(children);\n const debounceChildren = useDebounce(children, 100);\n const containerRef = React.useRef<HTMLSpanElement>(null);\n const [width, setWidth] = React.useState<number | \"auto\">(\"auto\");\n\n const transition: Transition = {\n duration,\n ease,\n };\n\n const previous = previousRef.current;\n React.useEffect(() => {\n previousRef.current = debounceChildren;\n }, [debounceChildren]);\n\n React.useEffect(() => {\n if (containerRef.current) {\n const resizeObserver = new ResizeObserver((entries) => {\n const observedWidth = entries[0]?.contentRect.width;\n if (observedWidth) setWidth(observedWidth);\n });\n\n resizeObserver.observe(containerRef.current);\n\n return () => {\n resizeObserver.disconnect();\n };\n }\n return () => {};\n }, []);\n\n const lcs = findLCS(previous, children);\n const prefixEnd = lcs ? children.indexOf(lcs) : 0;\n const suffixStart = lcs ? prefixEnd + lcs.length : 0;\n\n const prefix = children.slice(0, prefixEnd);\n const suffix = children.slice(suffixStart);\n\n const shiftDistance = \"0.125em\";\n\n const renderChars = (key: string, text: string, originX: number) => (\n <motion.span\n style={styles.span}\n key={`${id}-${text}`}\n layout=\"position\"\n layoutId={`${id}-${text}`}\n exit={{\n opacity: 0,\n scale: 0.95,\n }}\n transition={transition}\n onAnimationComplete={onAnimationComplete}\n >\n <AnimatePresence initial={originX !== 0.5} mode=\"popLayout\">\n <motion.span\n style={styles.span}\n key={`${id}-${key}-${text}`}\n initial={{\n x:\n originX === 0 ? shiftDistance : originX === 1 ? shiftDistance : 0,\n opacity: 0,\n scale: 0.95,\n }}\n animate={{\n x: 0,\n opacity: 1,\n scale: 1,\n }}\n transition={{\n ...transition,\n delay: (transition.duration ?? defaultConfig.duration) * 0.2,\n }}\n >\n {text}\n </motion.span>\n </AnimatePresence>\n </motion.span>\n );\n\n const animationProps = {\n width,\n //fontSize: fontSize ? `${fontSize}px` : undefined,\n //fontWeight: fontWeight ? fontWeight : undefined,\n //fontVariationSettings: fontWeight ? `\"wght\" ${fontWeight}` : undefined,\n };\n\n return (\n <motion.span\n style={{ ...styles.container, ...(debug ? styles.debug : {}) }}\n initial={animationProps}\n animate={animationProps}\n transition={transition}\n >\n <span ref={containerRef} style={styles.span}>\n <AnimatePresence initial={false} mode=\"popLayout\">\n <motion.span\n key={`${id}-torph`}\n layoutId={`${id}-torph`}\n layout=\"position\"\n transition={transition}\n style={styles.span}\n >\n <AnimatePresence initial={false} mode=\"popLayout\">\n {prefix && renderChars(`${id}-prefix`, prefix, 0)}\n {lcs && (\n <motion.span\n layout=\"position\"\n layoutId={`${id}-lcs`}\n transition={transition}\n style={{ ...styles.span }}\n >\n {renderChars(`${id}-lcs-chars`, lcs, 0.5)}\n </motion.span>\n )}\n {suffix && renderChars(`${id}-suffix`, suffix, 1)}\n </AnimatePresence>\n </motion.span>\n </AnimatePresence>\n </span>\n </motion.span>\n );\n};\n","const styles: { [key: string]: React.CSSProperties } = {\n container: {\n position: \"relative\",\n display: \"inline-flex\",\n },\n span: {\n position: \"relative\",\n display: \"inline-flex\",\n whiteSpace: \"pre\",\n },\n debug: {\n outline: \"1px solid red\",\n },\n};\n\nexport default styles;\n","import { TextMorphProps } from \"./components/text-morph/types\";\n\ntype DefaultTextMorphProps = Omit<TextMorphProps, \"duration\" | \"children\"> & {\n duration: NonNullable<TextMorphProps[\"duration\"]>;\n children?: TextMorphProps[\"children\"];\n};\n\nconst defaultConfig: DefaultTextMorphProps = {\n duration: 0.4,\n ease: [0.19, 1, 0.22, 1],\n respectMotionPreference: true,\n};\n\nexport default defaultConfig;\n","export const findLCS = (a: string, b: string): string => {\n const m = a.length;\n const n = b.length;\n const dp: number[][] = Array.from({ length: m + 1 }, () =>\n Array(n + 1).fill(0),\n );\n let maxLength = 0;\n let endIndex = 0;\n\n for (let i = 1; i <= m; i += 1) {\n for (let j = 1; j <= n; j += 1) {\n if (a[i - 1] === b[j - 1]) {\n // @ts-expect-error Property 'dp' does not exist on type 'number[][]'.\n dp[i][j] = dp[i - 1][j - 1] + 1;\n // @ts-expect-error Property 'dp' does not exist on type 'number[][]'.\n if (dp[i][j] > maxLength) {\n // @ts-expect-error Property 'dp' does not exist on type 'number[][]'.\n maxLength = dp[i][j];\n endIndex = i;\n }\n }\n }\n }\n return a.substring(endIndex - maxLength, endIndex);\n};\n\nexport const debug = false;\n","import React from \"react\";\n\nconst useDebounce = (value: string, delay: number) => {\n const [debouncedValue, setDebouncedValue] = React.useState(value);\n\n React.useEffect(() => {\n const handler = setTimeout(() => {\n setDebouncedValue(value);\n }, delay);\n\n return () => {\n clearTimeout(handler);\n };\n }, [value, delay]);\n\n return debouncedValue;\n};\n\nexport default useDebounce;\n"],"mappings":"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","names":["index_exports","__export","TextMorph","version","__toCommonJS","version","import_react","styles","styles_default","defaultConfig","config_default","findLCS","a","b","m","n","dp","maxLength","endIndex","i","j","import_react","useDebounce","value","delay","debouncedValue","setDebouncedValue","React","handler","useDebounce_default","import_jsx_runtime","TextMorph","debug","children","duration","config_default","ease","onAnimationComplete","id","React","previousRef","debounceChildren","useDebounce_default","containerRef","width","setWidth","transition","previous","resizeObserver","entries","observedWidth","lcs","findLCS","prefixEnd","suffixStart","prefix","suffix","shiftDistance","renderChars","key","text","originX","styles_default","animationProps"]}
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.2\",\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\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 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 (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 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 t}from"./chunk-SZ3JXWX5.mjs";import"./chunk-I42X5RZV.mjs";import"./chunk-66KQLHAM.mjs";import"./chunk-2RJBUJEF.mjs";import"./chunk-Y6MZSIYO.mjs";import"./chunk-RM2STLUA.mjs";var i="0.0.1";export{t as TextMorph,i as version};
2
+ import{a as e}from"./chunk-KHX5JMQ4.mjs";var i="0.0.2";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.1\",\n \"description\": \"An animated text component for React.\",\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 \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.js\"\n },\n \"./*\": {\n \"import\": \"./dist/components/*/index.mjs\",\n \"require\": \"./dist/components/*/index.js\",\n \"types\": \"./dist/components/*/index.d.ts\"\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 \"text\",\n \"animation\"\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 },\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 \"tsup\": \"^8.5.0\",\n \"typescript\": \"^5.0.2\"\n },\n \"dependencies\": {\n \"motion\": \"^12.23.16\"\n }\n}\n"],"mappings":";0LAEE,IAAAA,EAAW","names":["version"]}
1
+ {"version":3,"sources":["../package.json"],"sourcesContent":["{\n \"name\": \"torph\",\n \"version\": \"0.0.2\",\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"]}
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { T as TextMorphOptions } from '../types-CsvRfPun.mjs';
3
+
4
+ type TextMorphProps = Omit<TextMorphOptions, "element"> & {
5
+ children: string;
6
+ };
7
+ declare const TextMorph: ({ children, ...props }: TextMorphProps) => React.JSX.Element;
8
+ declare function useTextMorph(props: Omit<TextMorphOptions, "element">): {
9
+ ref: React.MutableRefObject<HTMLDivElement | null>;
10
+ update: (text: string) => void;
11
+ };
12
+
13
+ export { TextMorph, type TextMorphProps, useTextMorph };
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { T as TextMorphOptions } from '../types-CsvRfPun.js';
3
+
4
+ type TextMorphProps = Omit<TextMorphOptions, "element"> & {
5
+ children: string;
6
+ };
7
+ declare const TextMorph: ({ children, ...props }: TextMorphProps) => React.JSX.Element;
8
+ declare function useTextMorph(props: Omit<TextMorphOptions, "element">): {
9
+ ref: React.MutableRefObject<HTMLDivElement | null>;
10
+ update: (text: string) => void;
11
+ };
12
+
13
+ export { TextMorph, type TextMorphProps, useTextMorph };
@@ -0,0 +1,25 @@
1
+ "use client";
2
+ "use strict";var C=Object.create;var M=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var B=Object.getOwnPropertyNames;var W=Object.getPrototypeOf,I=Object.prototype.hasOwnProperty;var X=(r,e)=>{for(var t in e)M(r,t,{get:e[t],enumerable:!0})},w=(r,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of B(e))!I.call(r,s)&&s!==t&&M(r,s,{get:()=>e[s],enumerable:!(o=k(e,s))||o.enumerable});return r};var Y=(r,e,t)=>(t=r!=null?C(W(r)):{},w(e||!r||!r.__esModule?M(t,"default",{value:r,enumerable:!0}):t,r)),D=r=>w(M({},"__esModule",{value:!0}),r);var P={};X(P,{TextMorph:()=>H,useTextMorph:()=>v});module.exports=D(P);var f=Y(require("react"));var b=class r{element;options={};data;currentMeasures={};prevMeasures={};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 o=t.offsetWidth,s=t.offsetHeight,i=e.includes(" "),g=new Intl.Segmenter(this.options.locale,{granularity:i?"word":"grapheme"}).segment(e)[Symbol.iterator](),m=this.blocks(g);this.prevMeasures=this.measure();let c=Array.from(t.children),u=new Set(m.map(n=>n.id)),p=c.filter(n=>!u.has(n.getAttribute("torph-id"))&&!n.hasAttribute("torph-exiting")),d=this.getUnscaledBoundingClientRect(t);if(p.forEach(n=>{let a=this.getUnscaledBoundingClientRect(n);n.setAttribute("torph-exiting",""),n.style.position="absolute",n.style.pointerEvents="none",n.style.left=`${a.left-d.left}px`,n.style.top=`${a.top-d.top}px`,n.style.width=`${a.width}px`,n.style.height=`${a.height}px`}),c.forEach(n=>{let a=n.getAttribute("torph-id");u.has(a)&&n.remove()}),m.forEach(n=>{let a=document.createElement("span");a.setAttribute("torph-item",""),a.setAttribute("torph-id",n.id),a.textContent=n.string,t.appendChild(a)}),this.currentMeasures=this.measure(),this.updateStyles(),p.forEach(n=>{let a=n.getAttribute("torph-id"),T=this.prevMeasures[a],A=Array.from(t.children).find(E=>{let R=E.getBoundingClientRect(),O=n.getBoundingClientRect();return Math.abs(R.left-O.left)<40}),x=A?this.currentMeasures[A.getAttribute("torph-id")]:T,S=(x?x.x-(T?.x||0):0)*.5,$=(x?x.y-(T?.y||0):0)*.5;n.getAnimations().forEach(E=>E.cancel());let L=n.animate({transform:`translate(${S}px, ${$}px) scale(0.95)`,opacity:0,offset:1},{duration:this.options.duration,easing:this.options.ease,fill:"both"});L.onfinish=()=>n.remove()}),o===0||s===0)return;t.style.width="auto",t.style.height="auto",t.offsetWidth;let l=t.offsetWidth,y=t.offsetHeight;t.style.width=`${o}px`,t.style.height=`${s}px`,t.offsetWidth,t.style.width=`${l}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((o,s)=>{if(o.hasAttribute("torph-exiting"))return;let i=o.getAttribute("torph-id")||`child-${s}`;t[i]={x:o.offsetLeft,y:o.offsetTop}}),t}updateStyles(){Array.from(this.element.children).forEach((t,o)=>{if(t.hasAttribute("torph-exiting"))return;let s=t.getAttribute("torph-id")||`child-${o}`,i=this.prevMeasures[s],h=this.currentMeasures[s],g=h?.x||0,m=h?.y||0,c=i?i?.x-g:0,u=i?i?.y-m:0,p=!i;t.getAnimations().forEach(d=>d.cancel()),t.animate({transform:`translate(${c}px, ${u}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(r.styleEl)return;let e=document.createElement("style");e.dataset.torph="true",e.innerHTML=`
3
+ [torph-root] {
4
+ display: inline-flex; /* TODO: remove for multi-line support */
5
+ position: relative;
6
+ will-change: width, height;
7
+ transition-property: width, height;
8
+ }
9
+
10
+ [torph-item] {
11
+ display: inline-block;
12
+ will-change: opacity, transform;
13
+ transform: none;
14
+ opacity: 1;
15
+ }
16
+
17
+ [torph-root][torph-debug] {
18
+ outline:2px solid magenta;
19
+ [torph-item] {
20
+ outline:2px solid cyan;
21
+ outline-offset: -4px;
22
+ }
23
+ }
24
+ `,document.head.appendChild(e),r.styleEl=e}removeStyles(){r.styleEl&&(r.styleEl.remove(),r.styleEl=void 0)}blocks(e){return Array.from(e).reduce((o,s)=>s.segment===" "?[...o,{id:`space-${s.index}`,string:"\xA0"}]:o.find(h=>h.string===s.segment)?[...o,{id:`${s.segment}-${s.index}`,string:s.segment}]:[...o,{id:s.segment,string:s.segment}],[])}getUnscaledBoundingClientRect(e){let t=e.getBoundingClientRect(),s=window.getComputedStyle(e).transform,i=1,h=1,g=/matrix\(([^)]+)\)/,m=s.match(g);if(m){let l=m[1]?.split(",").map(Number);l&&l?.length>=4&&(i=l[0],h=l[3])}else{let l=s.match(/scaleX\(([^)]+)\)/),y=s.match(/scaleY\(([^)]+)\)/);l&&(i=parseFloat(l[1])),y&&(h=parseFloat(y[1]))}let c=t.width/i,u=t.height/h,p=t.x+(t.width-c)/2,d=t.y+(t.height-u)/2;return{x:p,y:d,width:c,height:u,top:d,right:p+c,bottom:d+u,left:p}}log(...e){this.options.debug&&console.log("[TextMorph]",...e)}};var H=({children:r,...e})=>{let{ref:t,update:o}=v(e);return f.default.useEffect(()=>{o(r)},[r,o]),f.default.createElement("div",{ref:t})};function v(r){let e=f.default.useRef(null),t=f.default.useRef(null);return f.default.useEffect(()=>(e.current&&(t.current=new b({element:e.current,...r})),()=>{t.current?.destroy()}),[]),{ref:e,update:s=>{t.current?.update(s)}}}0&&(module.exports={TextMorph,useTextMorph});
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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\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 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 (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 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"]}