torph 0.0.1 → 0.0.3
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.
- package/README.md +114 -12
- package/dist/.DS_Store +0 -0
- package/dist/TextMorph-TPUHANOZ.vue +47 -0
- package/dist/chunk-43OPPES3.mjs +25 -0
- package/dist/chunk-43OPPES3.mjs.map +1 -0
- package/dist/index.d.mts +24 -6
- package/dist/index.d.ts +24 -6
- package/dist/index.js +23 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +13 -0
- package/dist/react/index.d.ts +13 -0
- package/dist/react/index.js +25 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +3 -0
- package/dist/react/index.mjs.map +1 -0
- package/dist/svelte/index.js +2 -0
- package/dist/svelte/index.js.map +1 -0
- package/dist/svelte/index.mjs +2 -0
- package/dist/svelte/index.mjs.map +1 -0
- package/dist/types-CsvRfPun.d.mts +9 -0
- package/dist/types-CsvRfPun.d.ts +9 -0
- package/dist/vue/index.js +2 -0
- package/dist/vue/index.js.map +1 -0
- package/dist/vue/index.mjs +2 -0
- package/dist/vue/index.mjs.map +1 -0
- package/package.json +41 -12
- package/dist/chunk-2RJBUJEF.mjs +0 -3
- package/dist/chunk-2RJBUJEF.mjs.map +0 -1
- package/dist/chunk-66KQLHAM.mjs +0 -3
- package/dist/chunk-66KQLHAM.mjs.map +0 -1
- package/dist/chunk-I42X5RZV.mjs +0 -3
- package/dist/chunk-I42X5RZV.mjs.map +0 -1
- package/dist/chunk-RM2STLUA.mjs +0 -2
- package/dist/chunk-RM2STLUA.mjs.map +0 -1
- package/dist/chunk-SZ3JXWX5.mjs +0 -3
- package/dist/chunk-SZ3JXWX5.mjs.map +0 -1
- package/dist/chunk-Y6MZSIYO.mjs +0 -3
- package/dist/chunk-Y6MZSIYO.mjs.map +0 -1
- package/dist/components/text-morph/index.d.mts +0 -7
- package/dist/components/text-morph/index.d.ts +0 -7
- package/dist/components/text-morph/index.js +0 -3
- package/dist/components/text-morph/index.js.map +0 -1
- package/dist/components/text-morph/index.mjs +0 -3
- package/dist/components/text-morph/index.mjs.map +0 -1
- package/dist/components/text-morph/styles.d.mts +0 -5
- package/dist/components/text-morph/styles.d.ts +0 -5
- package/dist/components/text-morph/styles.js +0 -3
- package/dist/components/text-morph/styles.js.map +0 -1
- package/dist/components/text-morph/styles.mjs +0 -3
- package/dist/components/text-morph/styles.mjs.map +0 -1
- package/dist/components/text-morph/types.d.mts +0 -12
- package/dist/components/text-morph/types.d.ts +0 -12
- package/dist/components/text-morph/types.js +0 -3
- package/dist/components/text-morph/types.js.map +0 -1
- package/dist/components/text-morph/types.mjs +0 -3
- package/dist/components/text-morph/types.mjs.map +0 -1
- package/dist/config.d.mts +0 -10
- package/dist/config.d.ts +0 -10
- package/dist/config.js +0 -3
- package/dist/config.js.map +0 -1
- package/dist/config.mjs +0 -3
- package/dist/config.mjs.map +0 -1
- package/dist/hooks/useDebounce.d.mts +0 -3
- package/dist/hooks/useDebounce.d.ts +0 -3
- package/dist/hooks/useDebounce.js +0 -3
- package/dist/hooks/useDebounce.js.map +0 -1
- package/dist/hooks/useDebounce.mjs +0 -3
- package/dist/hooks/useDebounce.mjs.map +0 -1
- package/dist/utils.d.mts +0 -4
- package/dist/utils.d.ts +0 -4
- package/dist/utils.js +0 -3
- package/dist/utils.js.map +0 -1
- package/dist/utils.mjs +0 -3
- package/dist/utils.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,37 +1,139 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Torph
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Animated text morphing component for React, Vue, and Svelte.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
-
```
|
|
8
|
-
|
|
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
|
|
20
|
+
import { TextMorph } from 'torph/react';
|
|
21
|
+
|
|
22
|
+
function App() {
|
|
23
|
+
const [text, setText] = useState('Hello World');
|
|
15
24
|
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
24
|
-
duration={
|
|
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
|
-
|
|
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
|
-
|
|
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 v=class g{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(" "),m=new Intl.Segmenter(this.options.locale,{granularity:r?"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 o=this.getUnscaledBoundingClientRect(s);s.setAttribute("torph-exiting",""),s.style.position="absolute",s.style.pointerEvents="none",s.style.left=`${o.left-u.left}px`,s.style.top=`${o.top-u.top}px`,s.style.width=`${o.width}px`,s.style.height=`${o.height}px`}),c.forEach(s=>{let o=s.getAttribute("torph-id");p.has(o)&&s.remove()}),d.forEach(s=>{let o=document.createElement("span");o.setAttribute("torph-item",""),o.setAttribute("torph-id",s.id),o.textContent=s.string,t.appendChild(o)}),this.currentMeasures=this.measure(),this.updateStyles(),a.forEach(s=>{if(this.isInitialRender){s.remove();return}let o=s.getAttribute("torph-id"),x=this.prevMeasures[o],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,E=(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(${E}px, ${A}px) scale(0.95)`,opacity:0,offset:1},{duration:this.options.duration,easing:this.options.ease,fill:"both"});w.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 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 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],h=this.currentMeasures[i],m=h?.x||0,d=h?.y||0,c=r?r?.x-m:0,p=r?r?.y-d:0,a=!r;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,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
|
|
@@ -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 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,8 +1,26 @@
|
|
|
1
|
-
|
|
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.
|
|
3
|
+
var version = "0.0.3";
|
|
7
4
|
|
|
8
|
-
|
|
5
|
+
declare class TextMorph {
|
|
6
|
+
private element;
|
|
7
|
+
private options;
|
|
8
|
+
private data;
|
|
9
|
+
private currentMeasures;
|
|
10
|
+
private prevMeasures;
|
|
11
|
+
private isInitialRender;
|
|
12
|
+
static styleEl: HTMLStyleElement;
|
|
13
|
+
constructor(options: TextMorphOptions);
|
|
14
|
+
destroy(): void;
|
|
15
|
+
update(value: HTMLElement | string): void;
|
|
16
|
+
private createTextGroup;
|
|
17
|
+
private measure;
|
|
18
|
+
private updateStyles;
|
|
19
|
+
private addStyles;
|
|
20
|
+
private removeStyles;
|
|
21
|
+
private blocks;
|
|
22
|
+
private getUnscaledBoundingClientRect;
|
|
23
|
+
private log;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { TextMorph, TextMorphOptions, version };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
|
-
|
|
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.
|
|
3
|
+
var version = "0.0.3";
|
|
7
4
|
|
|
8
|
-
|
|
5
|
+
declare class TextMorph {
|
|
6
|
+
private element;
|
|
7
|
+
private options;
|
|
8
|
+
private data;
|
|
9
|
+
private currentMeasures;
|
|
10
|
+
private prevMeasures;
|
|
11
|
+
private isInitialRender;
|
|
12
|
+
static styleEl: HTMLStyleElement;
|
|
13
|
+
constructor(options: TextMorphOptions);
|
|
14
|
+
destroy(): void;
|
|
15
|
+
update(value: HTMLElement | string): void;
|
|
16
|
+
private createTextGroup;
|
|
17
|
+
private measure;
|
|
18
|
+
private updateStyles;
|
|
19
|
+
private addStyles;
|
|
20
|
+
private removeStyles;
|
|
21
|
+
private blocks;
|
|
22
|
+
private getUnscaledBoundingClientRect;
|
|
23
|
+
private log;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { TextMorph, TextMorphOptions, version };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
"use strict";var
|
|
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=`
|
|
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,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});
|
|
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.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"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import{a as
|
|
2
|
+
import{a as e}from"./chunk-43OPPES3.mjs";var i="0.0.3";export{e as TextMorph,i as version};
|
|
3
3
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../package.json"],"sourcesContent":["{\n \"name\": \"torph\",\n \"version\": \"0.0.
|
|
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"]}
|
|
@@ -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 I=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var B=Object.getPrototypeOf,W=Object.prototype.hasOwnProperty;var X=(r,e)=>{for(var t in e)M(r,t,{get:e[t],enumerable:!0})},A=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of k(e))!W.call(r,s)&&s!==t&&M(r,s,{get:()=>e[s],enumerable:!(n=I(e,s))||n.enumerable});return r};var Y=(r,e,t)=>(t=r!=null?C(B(r)):{},A(e||!r||!r.__esModule?M(t,"default",{value:r,enumerable:!0}):t,r)),D=r=>A(M({},"__esModule",{value:!0}),r);var P={};X(P,{TextMorph:()=>H,useTextMorph:()=>E});module.exports=D(P);var f=Y(require("react"));var b=class r{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,s=t.offsetHeight,o=e.includes(" "),g=new Intl.Segmenter(this.options.locale,{granularity:o?"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(i=>i.id)),p=c.filter(i=>!u.has(i.getAttribute("torph-id"))&&!i.hasAttribute("torph-exiting")),d=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-d.left}px`,i.style.top=`${a.top-d.top}px`,i.style.width=`${a.width}px`,i.style.height=`${a.height}px`}),c.forEach(i=>{let a=i.getAttribute("torph-id");u.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=>{if(this.isInitialRender){i.remove();return}let a=i.getAttribute("torph-id"),T=this.prevMeasures[a],w=Array.from(t.children).find(v=>{let L=v.getBoundingClientRect(),O=i.getBoundingClientRect();return Math.abs(L.left-O.left)<40}),x=w?this.currentMeasures[w.getAttribute("torph-id")]:T,R=(x?x.x-(T?.x||0):0)*.5,S=(x?x.y-(T?.y||0):0)*.5;i.getAnimations().forEach(v=>v.cancel());let $=i.animate({transform:`translate(${R}px, ${S}px) scale(0.95)`,opacity:0,offset:1},{duration:this.options.duration,easing:this.options.ease,fill:"both"});$.onfinish=()=>i.remove()}),this.isInitialRender){this.isInitialRender=!1,t.style.width="auto",t.style.height="auto";return}if(n===0||s===0)return;t.style.width="auto",t.style.height="auto",t.offsetWidth;let l=t.offsetWidth,y=t.offsetHeight;t.style.width=`${n}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((n,s)=>{if(n.hasAttribute("torph-exiting"))return;let o=n.getAttribute("torph-id")||`child-${s}`;t[o]={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 s=t.getAttribute("torph-id")||`child-${n}`,o=this.prevMeasures[s],h=this.currentMeasures[s],g=h?.x||0,m=h?.y||0,c=o?o?.x-g:0,u=o?o?.y-m:0,p=!o;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((n,s)=>s.segment===" "?[...n,{id:`space-${s.index}`,string:"\xA0"}]:n.find(h=>h.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,o=1,h=1,g=/matrix\(([^)]+)\)/,m=s.match(g);if(m){let l=m[1]?.split(",").map(Number);l&&l?.length>=4&&(o=l[0],h=l[3])}else{let l=s.match(/scaleX\(([^)]+)\)/),y=s.match(/scaleY\(([^)]+)\)/);l&&(o=parseFloat(l[1])),y&&(h=parseFloat(y[1]))}let c=t.width/o,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:n}=E(e);return f.default.useEffect(()=>{n(r)},[r,n]),f.default.createElement("div",{ref:t})};function E(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
|