@liiift-studio/magnettype 1.1.6 → 1.2.0

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 CHANGED
@@ -30,7 +30,7 @@ Per-word cursor-proximity weight variation driven by a continuous rAF loop.
30
30
  import { MagnetTypeText } from '@liiift-studio/magnettype'
31
31
 
32
32
  <MagnetTypeText
33
- mode="field"
33
+ mode="word"
34
34
  axes={{ wght: [300, 700] }}
35
35
  radius={150}
36
36
  falloff="quadratic"
@@ -40,43 +40,43 @@ import { MagnetTypeText } from '@liiift-studio/magnettype'
40
40
  </MagnetTypeText>
41
41
  ```
42
42
 
43
- ### React — block mode (`MagnetBlock`)
43
+ ### React — block mode (`MagnetChar`)
44
44
 
45
45
  Per-character cursor-proximity weight variation. Works with mixed content (inline elements, links, `<code>`, etc.) inside any block element. Characters are wrapped as React elements — no DOM mutation.
46
46
 
47
47
  ```tsx
48
- import { MagnetBlock } from '@liiift-studio/magnettype'
48
+ import { MagnetChar } from '@liiift-studio/magnettype'
49
49
 
50
50
  // Per-character spread — each character responds to cursor distance
51
- <MagnetBlock
51
+ <MagnetChar
52
52
  spreadRadius={200}
53
53
  minWeight={300}
54
54
  maxWeight={700}
55
55
  >
56
56
  Typography that responds to presence.
57
- </MagnetBlock>
57
+ </MagnetChar>
58
58
 
59
59
  // Whole-element gate — the effect only activates when the cursor is within proximityRadius of the element edge
60
- <MagnetBlock
60
+ <MagnetChar
61
61
  proximityRadius={120}
62
62
  minWeight={300}
63
63
  maxWeight={700}
64
64
  >
65
65
  Weight rises when the cursor enters.
66
- </MagnetBlock>
66
+ </MagnetChar>
67
67
 
68
68
  // Both combined — proximity gates the spread effect
69
- <MagnetBlock
69
+ <MagnetChar
70
70
  proximityRadius={200}
71
71
  spreadRadius={120}
72
72
  minWeight={300}
73
73
  maxWeight={700}
74
74
  >
75
75
  Only spreads when the cursor is close.
76
- </MagnetBlock>
76
+ </MagnetChar>
77
77
  ```
78
78
 
79
- **`MagnetBlock` props:**
79
+ **`MagnetChar` props:**
80
80
 
81
81
  | Prop | Default | Description |
82
82
  |------|---------|-------------|
@@ -97,7 +97,7 @@ import { MagnetBlock } from '@liiift-studio/magnettype'
97
97
  ```tsx
98
98
  import { useMagnetType } from '@liiift-studio/magnettype'
99
99
 
100
- const ref = useMagnetType({ mode: 'field', axes: { wght: [300, 700] }, radius: 150 })
100
+ const ref = useMagnetType({ mode: 'word', axes: { wght: [300, 700] }, radius: 150 })
101
101
  return <p ref={ref}>{children}</p>
102
102
  ```
103
103
 
@@ -120,7 +120,7 @@ import { startMagnetType, removeMagnetType, getCleanHTML } from '@liiift-studio/
120
120
 
121
121
  const el = document.querySelector('p')
122
122
  const original = getCleanHTML(el)
123
- const opts = { mode: 'field', axes: { wght: [300, 700] }, radius: 150 }
123
+ const opts = { mode: 'word', axes: { wght: [300, 700] }, radius: 150 }
124
124
 
125
125
  let stop
126
126
 
@@ -163,10 +163,10 @@ ro.observe(el)
163
163
  ### TypeScript
164
164
 
165
165
  ```ts
166
- import type { MagnetTypeOptions, FalloffType, MagnetModeType, MagnetBlockProps } from '@liiift-studio/magnettype'
166
+ import type { MagnetTypeOptions, FalloffType, MagnetModeType, MagnetCharProps } from '@liiift-studio/magnettype'
167
167
 
168
168
  const fieldOpts: MagnetTypeOptions = {
169
- mode: 'field',
169
+ mode: 'word',
170
170
  axes: { wght: [300, 700], wdth: [90, 110] },
171
171
  radius: 120,
172
172
  falloff: 'quadratic' as FalloffType,
@@ -185,7 +185,7 @@ const legibilityOpts: MagnetTypeOptions = {
185
185
 
186
186
  | Option | Default | Description |
187
187
  |--------|---------|-------------|
188
- | `mode` | `'field'` | `'field'` — cursor proximity drives per-word `font-variation-settings` via a continuous rAF loop. `'legibility'` — static per-character `wdth` boost on visually confusable characters |
188
+ | `mode` | `'word'` | `'word'` — cursor proximity drives per-word `font-variation-settings` via a continuous rAF loop. `'legibility'` — static per-character `wdth` boost on visually confusable characters |
189
189
  | `axes` | `{ wght: [300, 500] }` | *(field mode)* Map of axis tag → `[restValue, peakValue]` |
190
190
  | `radius` | `120` | *(field mode)* Pixel radius over which the field effect fades from each word's centre |
191
191
  | `falloff` | `'quadratic'` | *(field mode)* `'linear'` or `'quadratic'` falloff curve |
@@ -209,9 +209,9 @@ strength = normalised² (quadratic) or normalised (linear)
209
209
 
210
210
  Each word's `font-variation-settings` interpolates between `restValue` and `peakValue` at that strength. Reads are batched before writes on every frame to avoid layout thrashing. When the cursor leaves, one final frame resets all words to `restValue`.
211
211
 
212
- ### Block mode (`MagnetBlock`)
212
+ ### Block mode (`MagnetChar`)
213
213
 
214
- `MagnetBlock` splits string children into per-character `<span>` elements during the React render pass using `useMemo` — no DOM mutation. Callback refs collect each span element. On `mousemove` (and on `scroll`, using the stored last position), the component reads each span's `getBoundingClientRect`, computes cursor-to-character-centre distance, and sets `font-variation-settings` directly on the span's style. This is passive and batched per frame via the event handler.
214
+ `MagnetChar` splits string children into per-character `<span>` elements during the React render pass using `useMemo` — no DOM mutation. Callback refs collect each span element. On `mousemove` (and on `scroll`, using the stored last position), the component reads each span's `getBoundingClientRect`, computes cursor-to-character-centre distance, and sets `font-variation-settings` directly on the span's style. This is passive and batched per frame via the event handler.
215
215
 
216
216
  `proximityRadius` measures cursor distance to the element **edge** (not its centre) — useful as an outer gate so the effect only fires when the cursor is actually near the block. `spreadRadius` measures cursor distance to each **character centre** — controls how wide the weight gradient spreads around the cursor within the block. Both are independent and combinable.
217
217
 
@@ -247,4 +247,4 @@ The package itself has zero runtime dependencies. Do not remove this entry.
247
247
 
248
248
  ---
249
249
 
250
- Current version: 1.1.6
250
+ Current version: 1.2.0
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const h=require("react"),W=require("react/jsx-runtime"),rt={i:3,l:3,1:3,I:3,r:2,n:1,m:1,0:2,O:2,o:1,b:1,d:1,p:1,q:1,c:1,e:1},z={word:"mt-word",char:"mt-char",probe:"mt-probe"},K={axes:{wght:[300,500]},radius:120,falloff:"quadratic",magnetMode:"attract",wdthBoost:6,scope:"document"};function ot(t,n=[]){return t.nodeType===Node.TEXT_NODE?n.push(t):t.childNodes.forEach(o=>ot(o,n)),n}function tt(t,n,o){if(!t||t==="normal")return`"${n}" ${o}`;const E=new RegExp(`(["'])${n}\\1\\s+[\\d.eE+-]+`),y=`"${n}" ${o}`;return E.test(t)?t.replace(E,y):`${t}, ${y}`}function nt(t,n){let o=t;for(const[E,y]of Object.entries(n))o=tt(o,E,y);return o}function st(t,n,o){if(n.opacity!==void 0){const[E,y]=n.opacity;t.style.opacity=String(E+(y-E)*o)}n.italic===!0&&(t.style.fontStyle=o>.5?"italic":"")}function et(t,n){n.opacity!==void 0&&(t.style.opacity=String(n.opacity[0])),n.italic===!0&&(t.style.fontStyle="")}function ct(t){const n=t.cloneNode(!0),o=n.querySelectorAll(`.${z.word}, .${z.char}`);return Array.from(o).reverse().forEach(y=>{const d=y.parentNode;if(d){for(;y.firstChild;)d.insertBefore(y.firstChild,y);d.removeChild(y)}}),n.innerHTML}function mt(t,n){t.innerHTML=n}function it(t,n,o={}){var p,r;if(typeof window>"u")return()=>{};if(window.matchMedia("(prefers-reduced-motion: reduce)").matches)return t.innerHTML=n,()=>{};const E=o.wdthBoost??K.wdthBoost,y=o.radius??K.radius,d=o.falloff??K.falloff,L=o.scope??K.scope,F=o.props,m=o.transitionMs??0,S=o.cachePositions??!0,V=window.scrollY;t.innerHTML=n;const j=getComputedStyle(t).fontVariationSettings,N=j.match(/"wdth"\s+([\d.eE+-]+)/),k=N?parseFloat(N[1]):100,T=ot(t),g=[];for(const a of T){const C=a.textContent??"";if(!C||!C.split("").some(v=>v in rt))continue;const e=document.createDocumentFragment();for(const v of C){const i=rt[v];if(i===void 0){const u=e.lastChild;u&&u.nodeType===Node.TEXT_NODE?u.textContent+=v:e.appendChild(document.createTextNode(v))}else{const u=document.createElement("span");u.className=z.char,u.style.fontVariationSettings=tt(j,"wdth",k),u.textContent=v,e.appendChild(u),g.push({span:u,riskLevel:i})}}a.parentNode.replaceChild(e,a)}if(requestAnimationFrame(()=>{typeof window<"u"&&Math.abs(window.scrollY-V)>2&&window.scrollTo({top:V,behavior:"instant"})}),g.length===0)return()=>{};F&&g.forEach(({span:a})=>et(a,F));let A=[],H=!1;function _(){const a=window.scrollX,C=window.scrollY;A=g.map(({span:c})=>{const e=c.getBoundingClientRect();return{cx:(e.left+e.right)/2+a,cy:(e.top+e.bottom)/2+C}}),H=!0}let R=null;S&&(_(),R=new ResizeObserver(()=>{H=!1}),R.observe(t),(r=(p=document.fonts)==null?void 0:p.ready)==null||r.then(()=>{H=!1}));let q=-9999,B=-9999,$=!1,w=0,P=!0,b=null;function D(){if(!P)return;if(!$){g.forEach(({span:e})=>{m>0&&(e.style.transition=`font-variation-settings ${m}ms ease`),e.style.fontVariationSettings=tt(j,"wdth",k),F&&et(e,F)}),m>0&&(b!==null&&clearTimeout(b),b=setTimeout(()=>{g.forEach(({span:e})=>{e.style.transition=""}),b=null},m)),w=0;return}S&&!H&&_();const a=S?q+window.scrollX:q,C=S?B+window.scrollY:B,c=S?null:g.map(({span:e})=>e.getBoundingClientRect());g.forEach(({span:e,riskLevel:v},i)=>{e.style.transition="";let u,O;if(S)({cx:u,cy:O}=A[i]);else{const I=c[i];u=I.left+I.width/2,O=I.top+I.height/2}const M=Math.sqrt((a-u)**2+(C-O)**2),f=Math.max(0,1-M/y),x=d==="quadratic"?f*f:f,Y=E*(v/3)*x;e.style.fontVariationSettings=tt(j,"wdth",k+Y),F&&st(e,F,x)}),w=requestAnimationFrame(D)}function X(a){q=a.clientX,B=a.clientY,$||($=!0),w===0&&(w=requestAnimationFrame(D))}function J(){$=!1,w===0&&(w=requestAnimationFrame(D))}function Q(a){a.touches.length!==0&&(q=a.touches[0].clientX,B=a.touches[0].clientY,$||($=!0),w===0&&(w=requestAnimationFrame(D)))}function l(){$=!1,w===0&&(w=requestAnimationFrame(D))}const s=L==="document"?document:t;return s.addEventListener("mousemove",X),s.addEventListener("mouseleave",J),s.addEventListener("touchmove",Q,{passive:!0}),s.addEventListener("touchend",l),()=>{P=!1,cancelAnimationFrame(w),b!==null&&clearTimeout(b),R&&R.disconnect(),s.removeEventListener("mousemove",X),s.removeEventListener("mouseleave",J),s.removeEventListener("touchmove",Q),s.removeEventListener("touchend",l),t.innerHTML=n}}function lt(t,n,o={}){var a,C;if(typeof window>"u")return()=>{};if(window.matchMedia("(prefers-reduced-motion: reduce)").matches)return t.innerHTML=n,()=>{};const E=o.axes??K.axes,y=o.radius??K.radius,d=o.falloff??K.falloff,L=o.magnetMode??K.magnetMode,F=o.scope??K.scope,m=o.props,S=o.transitionMs??0,V=o.cachePositions??!0,j=o.stabilizeLayout??!0,N=window.scrollY;t.innerHTML=n;const k=ot(t),T=[];for(const c of k){const e=c.textContent??"";if(!e.trim())continue;const v=e.split(/(\S+)/),i=document.createDocumentFragment();for(let u=0;u<v.length;u+=2){const O=v[u],M=v[u+1];if(!M)continue;const x=v[u+3]===void 0?v[u+2]??"":"",Y=document.createElement("span");Y.className=z.word,Y.textContent=O+M+x,i.appendChild(Y),T.push(Y)}c.parentNode.replaceChild(i,c)}if(requestAnimationFrame(()=>{typeof window<"u"&&Math.abs(window.scrollY-N)>2&&window.scrollTo({top:N,behavior:"instant"})}),T.length===0)return()=>{};const g=getComputedStyle(t).fontVariationSettings,A=nt(g,Object.fromEntries(Object.entries(E).map(([c,[e]])=>[c,e])));let H=0;if(j){const c=nt(g,Object.fromEntries(Object.entries(E).map(([f,[,x]])=>[f,x]))),e=t.style.fontVariationSettings,v=t.style.whiteSpace,i=t.style.overflow;t.style.whiteSpace="nowrap",t.style.overflow="visible",t.style.fontVariationSettings=c;const u=t.scrollWidth;t.style.fontVariationSettings=A;const O=t.scrollWidth;t.style.fontVariationSettings=e,t.style.whiteSpace=v,t.style.overflow=i;const M=T.reduce((f,x)=>{var Y;return f+(((Y=x.textContent)==null?void 0:Y.replace(/\s+/g,"").length)??0)},0);M>0&&u>O&&(H=(u-O)/M)}T.forEach(c=>{c.style.fontVariationSettings=A,m&&et(c,m)});let _=[],R=!1;function q(){const c=window.scrollX,e=window.scrollY;_=T.map(v=>{const i=v.getBoundingClientRect();return{cx:(i.left+i.right)/2+c,cy:(i.top+i.bottom)/2+e}}),R=!0}let B=null;V&&(q(),B=new ResizeObserver(()=>{R=!1}),B.observe(t),(C=(a=document.fonts)==null?void 0:a.ready)==null||C.then(()=>{R=!1}));let $=-9999,w=-9999,P=!1,b=0,D=!0,X=null;function J(){if(!D)return;if(!P){T.forEach(i=>{S>0&&(i.style.transition=`font-variation-settings ${S}ms ease`),i.style.fontVariationSettings=A,j&&(i.style.letterSpacing=""),m&&et(i,m)}),S>0&&(X!==null&&clearTimeout(X),X=setTimeout(()=>{T.forEach(i=>{i.style.transition=""}),X=null},S)),b=0;return}V&&!R&&q();const c=V?$+window.scrollX:$,e=V?w+window.scrollY:w,v=V?null:T.map(i=>i.getBoundingClientRect());T.forEach((i,u)=>{i.style.transition="";let O,M;if(V)({cx:O,cy:M}=_[u]);else{const G=v[u];O=G.left+G.width/2,M=G.top+G.height/2}const f=Math.sqrt((c-O)**2+(e-M)**2),x=Math.max(0,1-f/y),Y=d==="quadratic"?x*x:x,I=L==="repel"?1-Y:Y,Z={};for(const G of Object.keys(E)){const[U,dt]=E[G]??[300,500];Z[G]=U+(dt-U)*I}i.style.fontVariationSettings=nt(g,Z),j&&H!==0&&(i.style.letterSpacing=`${(-H*I).toFixed(3)}px`),m&&st(i,m,Y)}),b=requestAnimationFrame(J)}function Q(c){$=c.clientX,w=c.clientY,P||(P=!0),b===0&&(b=requestAnimationFrame(J))}function l(){P=!1,b===0&&(b=requestAnimationFrame(J))}function s(c){c.touches.length!==0&&($=c.touches[0].clientX,w=c.touches[0].clientY,P||(P=!0),b===0&&(b=requestAnimationFrame(J)))}function p(){P=!1,b===0&&(b=requestAnimationFrame(J))}const r=F==="document"?document:t;return r.addEventListener("mousemove",Q),r.addEventListener("mouseleave",l),r.addEventListener("touchmove",s,{passive:!0}),r.addEventListener("touchend",p),()=>{D=!1,cancelAnimationFrame(b),X!==null&&clearTimeout(X),B&&B.disconnect(),r.removeEventListener("mousemove",Q),r.removeEventListener("mouseleave",l),r.removeEventListener("touchmove",s),r.removeEventListener("touchend",p),t.innerHTML=n}}function at(t){const n=h.useRef(null),o=h.useRef(null),E=h.useRef(t);E.current=t;const y=h.useRef(null),d=t.mode??"field",{axes:L,radius:F,falloff:m,magnetMode:S,wdthBoost:V,scope:j}=t,N=L?JSON.stringify(L):void 0,k=t.props?JSON.stringify(t.props):void 0,T=h.useCallback(()=>{const g=n.current;if(!g)return;o.current===null&&(o.current=ct(g)),y.current&&(y.current(),y.current=null),(E.current.mode??"field")==="field"?y.current=lt(g,o.current,E.current):y.current=it(g,o.current,E.current)},[d,N,F,m,S,V,j,k]);return h.useLayoutEffect(()=>(T(),()=>{y.current&&(y.current(),y.current=null)}),[T]),h.useEffect(()=>{var g,A;(A=(g=document.fonts)==null?void 0:g.ready)==null||A.then(T)},[T]),n}const ut=h.forwardRef(function({children:n,as:o="p",className:E,style:y,...d},L){const F=at(d),m=h.useCallback(S=>{F.current=S,typeof L=="function"?L(S):L&&(L.current=S)},[L]);return W.jsx(o,{ref:m,className:E,style:y,children:n})});ut.displayName="MagnetTypeText";const ft=h.forwardRef(function({children:n,as:o="p",className:E,style:y,minWeight:d=300,maxWeight:L=600,proximityRadius:F,spreadRadius:m,fixedAxes:S={},cachePositions:V=!0,rafThrottle:j=!0,stabilizeLayout:N=!0},k){const T=h.useRef(null),g=h.useRef(null),A=h.useRef([]),H=h.useRef([]),_=h.useRef(null),R=h.useRef(!1),q=h.useRef(null),B=h.useRef(0),$=h.useCallback(l=>{T.current=l,typeof k=="function"?k(l):k&&(k.current=l)},[k]);function w(l){const s=[`'wght' ${l.toFixed(0)}`];for(const[p,r]of Object.entries(S))s.push(`'${p}' ${r}`);return s.join(", ")}function P(){const l=T.current;if(!l)return;const s=window.scrollX,p=window.scrollY,r=l.getBoundingClientRect();_.current={top:r.top+p,left:r.left+s,right:r.right+s,bottom:r.bottom+p},H.current=A.current.map(a=>{if(!a)return{cx:0,cy:0};const C=a.getBoundingClientRect();return{cx:(C.left+C.right)/2+s,cy:(C.top+C.bottom)/2+p}}),R.current=!0}function b(l,s){const p=T.current;if(!p)return;V&&!R.current&&P();let r,a,C,c,e,v;if(V&&_.current)e=l+window.scrollX,v=s+window.scrollY,{top:r,left:a,right:C,bottom:c}=_.current;else{const M=p.getBoundingClientRect();e=l,v=s,r=M.top,a=M.left,C=M.right,c=M.bottom}const i=Math.max(a-e,0,e-C),u=Math.max(r-v,0,v-c),O=Math.sqrt(i*i+u*u);if(F!==void 0&&!m){const f=1-(1-Math.max(0,1-O/F))**2;p.style.fontVariationSettings=w(d+(L-d)*f);return}if(m){if(F!==void 0&&O>F){p.style.fontVariationSettings=w(d);for(const f of A.current)f&&(f.style.fontVariationSettings=w(d),N&&(f.style.letterSpacing=""));return}if(O>m){for(const f of A.current)f&&(f.style.fontVariationSettings=w(d),N&&(f.style.letterSpacing=""));return}const M=A.current;if(V&&H.current.length===M.length)for(let f=0;f<M.length;f++){const x=M[f];if(!x)continue;const{cx:Y,cy:I}=H.current[f],Z=Math.sqrt((e-Y)**2+(v-I)**2),U=1-(1-Math.max(0,1-Z/m))**2;x.style.fontVariationSettings=w(d+(L-d)*U),N&&B.current!==0&&(x.style.letterSpacing=`${(-B.current*U).toFixed(3)}px`)}else for(const f of M){if(!f)continue;const x=f.getBoundingClientRect(),Y=(x.left+x.right)/2,I=(x.top+x.bottom)/2,Z=Math.sqrt((l-Y)**2+(s-I)**2),U=1-(1-Math.max(0,1-Z/m))**2;f.style.fontVariationSettings=w(d+(L-d)*U),N&&B.current!==0&&(f.style.letterSpacing=`${(-B.current*U).toFixed(3)}px`)}}}const D=h.useCallback(l=>{g.current={x:l.clientX,y:l.clientY},j?q.current===null&&(q.current=requestAnimationFrame(()=>{q.current=null,g.current&&b(g.current.x,g.current.y)})):b(l.clientX,l.clientY)},[d,L,F,m,V,j,N,JSON.stringify(S)]),X=h.useCallback(()=>{g.current&&b(g.current.x,g.current.y)},[d,L,F,m,V,N,JSON.stringify(S)]),J=h.useCallback(()=>{g.current=null,q.current!==null&&(cancelAnimationFrame(q.current),q.current=null);const l=T.current;l&&(l.style.fontVariationSettings=w(d));for(const s of A.current)s&&(s.style.fontVariationSettings=w(d),N&&(s.style.letterSpacing=""))},[d,N,JSON.stringify(S)]);h.useEffect(()=>(window.addEventListener("mousemove",D,{passive:!0}),window.addEventListener("scroll",X,{passive:!0,capture:!0}),document.documentElement.addEventListener("mouseleave",J),()=>{window.removeEventListener("mousemove",D),window.removeEventListener("scroll",X,{capture:!0}),document.documentElement.removeEventListener("mouseleave",J),q.current!==null&&(cancelAnimationFrame(q.current),q.current=null)}),[D,X,J]),h.useEffect(()=>{var p,r;if(!V||!m)return;R.current=!1;const l=T.current;if(!l)return;const s=new ResizeObserver(()=>{R.current=!1});return s.observe(l),(r=(p=document.fonts)==null?void 0:p.ready)==null||r.then(()=>{R.current=!1}),()=>s.disconnect()},[V,m]),h.useEffect(()=>{R.current=!1},[n,m]),h.useEffect(()=>{var s,p;if(!N||!m){B.current=0;return}function l(){const r=T.current;if(!r)return;const a=r.textContent??"",C=a.replace(/\s/g,"").length;if(C===0)return;const c=getComputedStyle(r),e=document.createElement("span");e.style.cssText="position:fixed;top:-9999px;left:-9999px;visibility:hidden;white-space:nowrap;pointer-events:none;",e.style.fontFamily=c.fontFamily,e.style.fontSize=c.fontSize,e.style.fontWeight=c.fontWeight,e.style.lineHeight=c.lineHeight,e.style.letterSpacing=c.letterSpacing,e.textContent=a,document.body.appendChild(e);const v=O=>{const M=[`'wght' ${O.toFixed(0)}`];for(const[f,x]of Object.entries(S))M.push(`'${f}' ${x}`);return M.join(", ")};e.style.fontVariationSettings=v(L);const i=e.scrollWidth;e.style.fontVariationSettings=v(d);const u=e.scrollWidth;document.body.removeChild(e),B.current=i>u?(i-u)/C:0}l(),(p=(s=document.fonts)==null?void 0:s.ready)==null||p.then(l)},[N,m,d,L,n,JSON.stringify(S)]);const Q=h.useMemo(()=>{if(!m)return n;A.current=[];let l=0;function s(p){if(typeof p=="string")return[...p].map(r=>{if(/\s/.test(r))return r;const a=l++;return W.jsx("span",{ref:C=>{A.current[a]=C},style:{fontVariationSettings:w(d)},children:r},a)});if(Array.isArray(p))return p.map((r,a)=>W.jsx(h.Fragment,{children:s(r)},a));if(h.isValidElement(p)){const r=p;if(r.props.children!==void 0)return h.cloneElement(r,{},s(r.props.children))}return p}return s(n)},[n,m,d,JSON.stringify(S)]);return W.jsx(o,{ref:$,className:E,style:{fontVariationSettings:w(d),...y},children:Q})});ft.displayName="MagnetBlock";exports.MAGNET_TYPE_CLASSES=z;exports.MagnetBlock=ft;exports.MagnetTypeText=ut;exports.applyMagnetType=it;exports.getCleanHTML=ct;exports.removeMagnetType=mt;exports.startMagnetType=lt;exports.useMagnetType=at;
1
+ "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const h=require("react"),W=require("react/jsx-runtime"),st={i:3,l:3,1:3,I:3,r:2,n:1,m:1,0:2,O:2,o:1,b:1,d:1,p:1,q:1,c:1,e:1},z={word:"mt-word",char:"mt-char",probe:"mt-probe"},K={axes:{wght:[300,500]},radius:120,falloff:"quadratic",magnetMode:"attract",wdthBoost:6,scope:"document"};function ot(t,n=[]){return t.nodeType===Node.TEXT_NODE?n.push(t):t.childNodes.forEach(o=>ot(o,n)),n}function tt(t,n,o){if(!t||t==="normal")return`"${n}" ${o}`;const E=new RegExp(`(["'])${n}\\1\\s+[\\d.eE+-]+`),y=`"${n}" ${o}`;return E.test(t)?t.replace(E,y):`${t}, ${y}`}function nt(t,n){let o=t;for(const[E,y]of Object.entries(n))o=tt(o,E,y);return o}function ct(t,n,o){if(n.opacity!==void 0){const[E,y]=n.opacity;t.style.opacity=String(E+(y-E)*o)}n.italic===!0&&(t.style.fontStyle=o>.5?"italic":"")}function et(t,n){n.opacity!==void 0&&(t.style.opacity=String(n.opacity[0])),n.italic===!0&&(t.style.fontStyle="")}function it(t){const n=t.cloneNode(!0),o=n.querySelectorAll(`.${z.word}, .${z.char}`);return Array.from(o).reverse().forEach(y=>{const d=y.parentNode;if(d){for(;y.firstChild;)d.insertBefore(y.firstChild,y);d.removeChild(y)}}),n.innerHTML}function mt(t,n){t.innerHTML=n}function lt(t,n,o={}){var p,r;if(typeof window>"u")return()=>{};if(window.matchMedia("(prefers-reduced-motion: reduce)").matches)return t.innerHTML=n,()=>{};const E=o.wdthBoost??K.wdthBoost,y=o.radius??K.radius,d=o.falloff??K.falloff,L=o.scope??K.scope,F=o.props,m=o.transitionMs??0,S=o.cachePositions??!0,V=window.scrollY;t.innerHTML=n;const j=getComputedStyle(t).fontVariationSettings,N=j.match(/"wdth"\s+([\d.eE+-]+)/),X=N?parseFloat(N[1]):100,T=ot(t),g=[];for(const a of T){const C=a.textContent??"";if(!C||!C.split("").some(v=>v in st))continue;const e=document.createDocumentFragment();for(const v of C){const i=st[v];if(i===void 0){const u=e.lastChild;u&&u.nodeType===Node.TEXT_NODE?u.textContent+=v:e.appendChild(document.createTextNode(v))}else{const u=document.createElement("span");u.className=z.char,u.style.fontVariationSettings=tt(j,"wdth",X),u.textContent=v,e.appendChild(u),g.push({span:u,riskLevel:i})}}a.parentNode.replaceChild(e,a)}if(requestAnimationFrame(()=>{typeof window<"u"&&Math.abs(window.scrollY-V)>2&&window.scrollTo({top:V,behavior:"instant"})}),g.length===0)return()=>{};F&&g.forEach(({span:a})=>et(a,F));let A=[],H=!1;function _(){const a=window.scrollX,C=window.scrollY;A=g.map(({span:c})=>{const e=c.getBoundingClientRect();return{cx:(e.left+e.right)/2+a,cy:(e.top+e.bottom)/2+C}}),H=!0}let R=null;S&&(_(),R=new ResizeObserver(()=>{H=!1}),R.observe(t),(r=(p=document.fonts)==null?void 0:p.ready)==null||r.then(()=>{H=!1}));let q=-9999,$=-9999,B=!1,w=0,P=!0,b=null;function D(){if(!P)return;if(!B){g.forEach(({span:e})=>{m>0&&(e.style.transition=`font-variation-settings ${m}ms ease`),e.style.fontVariationSettings=tt(j,"wdth",X),F&&et(e,F)}),m>0&&(b!==null&&clearTimeout(b),b=setTimeout(()=>{g.forEach(({span:e})=>{e.style.transition=""}),b=null},m)),w=0;return}S&&!H&&_();const a=S?q+window.scrollX:q,C=S?$+window.scrollY:$,c=S?null:g.map(({span:e})=>e.getBoundingClientRect());g.forEach(({span:e,riskLevel:v},i)=>{e.style.transition="";let u,O;if(S)({cx:u,cy:O}=A[i]);else{const I=c[i];u=I.left+I.width/2,O=I.top+I.height/2}const M=Math.sqrt((a-u)**2+(C-O)**2),f=Math.max(0,1-M/y),x=d==="quadratic"?f*f:f,Y=E*(v/3)*x;e.style.fontVariationSettings=tt(j,"wdth",X+Y),F&&ct(e,F,x)}),w=requestAnimationFrame(D)}function k(a){q=a.clientX,$=a.clientY,B||(B=!0),w===0&&(w=requestAnimationFrame(D))}function J(){B=!1,w===0&&(w=requestAnimationFrame(D))}function Q(a){a.touches.length!==0&&(q=a.touches[0].clientX,$=a.touches[0].clientY,B||(B=!0),w===0&&(w=requestAnimationFrame(D)))}function l(){B=!1,w===0&&(w=requestAnimationFrame(D))}const s=L==="document"?document:t;return s.addEventListener("mousemove",k),s.addEventListener("mouseleave",J),s.addEventListener("touchmove",Q,{passive:!0}),s.addEventListener("touchend",l),()=>{P=!1,cancelAnimationFrame(w),b!==null&&clearTimeout(b),R&&R.disconnect(),s.removeEventListener("mousemove",k),s.removeEventListener("mouseleave",J),s.removeEventListener("touchmove",Q),s.removeEventListener("touchend",l),t.innerHTML=n}}function at(t,n,o={}){var a,C;if(typeof window>"u")return()=>{};if(window.matchMedia("(prefers-reduced-motion: reduce)").matches)return t.innerHTML=n,()=>{};const E=o.axes??K.axes,y=o.radius??K.radius,d=o.falloff??K.falloff,L=o.magnetMode??K.magnetMode,F=o.scope??K.scope,m=o.props,S=o.transitionMs??0,V=o.cachePositions??!0,j=o.stabilizeLayout??!0,N=window.scrollY;t.innerHTML=n;const X=ot(t),T=[];for(const c of X){const e=c.textContent??"";if(!e.trim())continue;const v=e.split(/(\S+)/),i=document.createDocumentFragment();for(let u=0;u<v.length;u+=2){const O=v[u],M=v[u+1];if(!M)continue;const x=v[u+3]===void 0?v[u+2]??"":"",Y=document.createElement("span");Y.className=z.word,Y.textContent=O+M+x,i.appendChild(Y),T.push(Y)}c.parentNode.replaceChild(i,c)}if(requestAnimationFrame(()=>{typeof window<"u"&&Math.abs(window.scrollY-N)>2&&window.scrollTo({top:N,behavior:"instant"})}),T.length===0)return()=>{};const g=getComputedStyle(t).fontVariationSettings,A=nt(g,Object.fromEntries(Object.entries(E).map(([c,[e]])=>[c,e])));let H=0;if(j){const c=nt(g,Object.fromEntries(Object.entries(E).map(([f,[,x]])=>[f,x]))),e=t.style.fontVariationSettings,v=t.style.whiteSpace,i=t.style.overflow;t.style.whiteSpace="nowrap",t.style.overflow="visible",t.style.fontVariationSettings=c;const u=t.scrollWidth;t.style.fontVariationSettings=A;const O=t.scrollWidth;t.style.fontVariationSettings=e,t.style.whiteSpace=v,t.style.overflow=i;const M=T.reduce((f,x)=>{var Y;return f+(((Y=x.textContent)==null?void 0:Y.replace(/\s+/g,"").length)??0)},0);M>0&&u>O&&(H=(u-O)/M)}T.forEach(c=>{c.style.fontVariationSettings=A,m&&et(c,m)});let _=[],R=!1;function q(){const c=window.scrollX,e=window.scrollY;_=T.map(v=>{const i=v.getBoundingClientRect();return{cx:(i.left+i.right)/2+c,cy:(i.top+i.bottom)/2+e}}),R=!0}let $=null;V&&(q(),$=new ResizeObserver(()=>{R=!1}),$.observe(t),(C=(a=document.fonts)==null?void 0:a.ready)==null||C.then(()=>{R=!1}));let B=-9999,w=-9999,P=!1,b=0,D=!0,k=null;function J(){if(!D)return;if(!P){T.forEach(i=>{S>0&&(i.style.transition=`font-variation-settings ${S}ms ease`),i.style.fontVariationSettings=A,j&&(i.style.letterSpacing=""),m&&et(i,m)}),S>0&&(k!==null&&clearTimeout(k),k=setTimeout(()=>{T.forEach(i=>{i.style.transition=""}),k=null},S)),b=0;return}V&&!R&&q();const c=V?B+window.scrollX:B,e=V?w+window.scrollY:w,v=V?null:T.map(i=>i.getBoundingClientRect());T.forEach((i,u)=>{i.style.transition="";let O,M;if(V)({cx:O,cy:M}=_[u]);else{const G=v[u];O=G.left+G.width/2,M=G.top+G.height/2}const f=Math.sqrt((c-O)**2+(e-M)**2),x=Math.max(0,1-f/y),Y=d==="quadratic"?x*x:x,I=L==="repel"?1-Y:Y,Z={};for(const G of Object.keys(E)){const[U,dt]=E[G]??[300,500];Z[G]=U+(dt-U)*I}i.style.fontVariationSettings=nt(g,Z),j&&H!==0&&(i.style.letterSpacing=`${(-H*I).toFixed(3)}px`),m&&ct(i,m,Y)}),b=requestAnimationFrame(J)}function Q(c){B=c.clientX,w=c.clientY,P||(P=!0),b===0&&(b=requestAnimationFrame(J))}function l(){P=!1,b===0&&(b=requestAnimationFrame(J))}function s(c){c.touches.length!==0&&(B=c.touches[0].clientX,w=c.touches[0].clientY,P||(P=!0),b===0&&(b=requestAnimationFrame(J)))}function p(){P=!1,b===0&&(b=requestAnimationFrame(J))}const r=F==="document"?document:t;return r.addEventListener("mousemove",Q),r.addEventListener("mouseleave",l),r.addEventListener("touchmove",s,{passive:!0}),r.addEventListener("touchend",p),()=>{D=!1,cancelAnimationFrame(b),k!==null&&clearTimeout(k),$&&$.disconnect(),r.removeEventListener("mousemove",Q),r.removeEventListener("mouseleave",l),r.removeEventListener("touchmove",s),r.removeEventListener("touchend",p),t.innerHTML=n}}function ut(t){const n=h.useRef(null),o=h.useRef(null),E=h.useRef(t);E.current=t;const y=h.useRef(null),d=t.mode??"word",{axes:L,radius:F,falloff:m,magnetMode:S,wdthBoost:V,scope:j}=t,N=L?JSON.stringify(L):void 0,X=t.props?JSON.stringify(t.props):void 0,T=h.useCallback(()=>{const g=n.current;if(!g)return;o.current===null&&(o.current=it(g)),y.current&&(y.current(),y.current=null),(E.current.mode??"word")==="word"?y.current=at(g,o.current,E.current):y.current=lt(g,o.current,E.current)},[d,N,F,m,S,V,j,X]);return h.useLayoutEffect(()=>(T(),()=>{y.current&&(y.current(),y.current=null)}),[T]),h.useEffect(()=>{var g,A;(A=(g=document.fonts)==null?void 0:g.ready)==null||A.then(T)},[T]),n}const ft=h.forwardRef(function({children:n,as:o="p",className:E,style:y,...d},L){const F=ut(d),m=h.useCallback(S=>{F.current=S,typeof L=="function"?L(S):L&&(L.current=S)},[L]);return W.jsx(o,{ref:m,className:E,style:y,children:n})});ft.displayName="MagnetTypeText";const rt=h.forwardRef(function({children:n,as:o="p",className:E,style:y,minWeight:d=300,maxWeight:L=600,proximityRadius:F,spreadRadius:m,fixedAxes:S={},cachePositions:V=!0,rafThrottle:j=!0,stabilizeLayout:N=!0},X){const T=h.useRef(null),g=h.useRef(null),A=h.useRef([]),H=h.useRef([]),_=h.useRef(null),R=h.useRef(!1),q=h.useRef(null),$=h.useRef(0),B=h.useCallback(l=>{T.current=l,typeof X=="function"?X(l):X&&(X.current=l)},[X]);function w(l){const s=[`'wght' ${l.toFixed(0)}`];for(const[p,r]of Object.entries(S))s.push(`'${p}' ${r}`);return s.join(", ")}function P(){const l=T.current;if(!l)return;const s=window.scrollX,p=window.scrollY,r=l.getBoundingClientRect();_.current={top:r.top+p,left:r.left+s,right:r.right+s,bottom:r.bottom+p},H.current=A.current.map(a=>{if(!a)return{cx:0,cy:0};const C=a.getBoundingClientRect();return{cx:(C.left+C.right)/2+s,cy:(C.top+C.bottom)/2+p}}),R.current=!0}function b(l,s){const p=T.current;if(!p)return;V&&!R.current&&P();let r,a,C,c,e,v;if(V&&_.current)e=l+window.scrollX,v=s+window.scrollY,{top:r,left:a,right:C,bottom:c}=_.current;else{const M=p.getBoundingClientRect();e=l,v=s,r=M.top,a=M.left,C=M.right,c=M.bottom}const i=Math.max(a-e,0,e-C),u=Math.max(r-v,0,v-c),O=Math.sqrt(i*i+u*u);if(F!==void 0&&!m){const f=1-(1-Math.max(0,1-O/F))**2;p.style.fontVariationSettings=w(d+(L-d)*f);return}if(m){if(F!==void 0&&O>F){p.style.fontVariationSettings=w(d);for(const f of A.current)f&&(f.style.fontVariationSettings=w(d),N&&(f.style.letterSpacing=""));return}if(O>m){for(const f of A.current)f&&(f.style.fontVariationSettings=w(d),N&&(f.style.letterSpacing=""));return}const M=A.current;if(V&&H.current.length===M.length)for(let f=0;f<M.length;f++){const x=M[f];if(!x)continue;const{cx:Y,cy:I}=H.current[f],Z=Math.sqrt((e-Y)**2+(v-I)**2),U=1-(1-Math.max(0,1-Z/m))**2;x.style.fontVariationSettings=w(d+(L-d)*U),N&&$.current!==0&&(x.style.letterSpacing=`${(-$.current*U).toFixed(3)}px`)}else for(const f of M){if(!f)continue;const x=f.getBoundingClientRect(),Y=(x.left+x.right)/2,I=(x.top+x.bottom)/2,Z=Math.sqrt((l-Y)**2+(s-I)**2),U=1-(1-Math.max(0,1-Z/m))**2;f.style.fontVariationSettings=w(d+(L-d)*U),N&&$.current!==0&&(f.style.letterSpacing=`${(-$.current*U).toFixed(3)}px`)}}}const D=h.useCallback(l=>{g.current={x:l.clientX,y:l.clientY},j?q.current===null&&(q.current=requestAnimationFrame(()=>{q.current=null,g.current&&b(g.current.x,g.current.y)})):b(l.clientX,l.clientY)},[d,L,F,m,V,j,N,JSON.stringify(S)]),k=h.useCallback(()=>{g.current&&b(g.current.x,g.current.y)},[d,L,F,m,V,N,JSON.stringify(S)]),J=h.useCallback(()=>{g.current=null,q.current!==null&&(cancelAnimationFrame(q.current),q.current=null);const l=T.current;l&&(l.style.fontVariationSettings=w(d));for(const s of A.current)s&&(s.style.fontVariationSettings=w(d),N&&(s.style.letterSpacing=""))},[d,N,JSON.stringify(S)]);h.useEffect(()=>(window.addEventListener("mousemove",D,{passive:!0}),window.addEventListener("scroll",k,{passive:!0,capture:!0}),document.documentElement.addEventListener("mouseleave",J),()=>{window.removeEventListener("mousemove",D),window.removeEventListener("scroll",k,{capture:!0}),document.documentElement.removeEventListener("mouseleave",J),q.current!==null&&(cancelAnimationFrame(q.current),q.current=null)}),[D,k,J]),h.useEffect(()=>{var p,r;if(!V||!m)return;R.current=!1;const l=T.current;if(!l)return;const s=new ResizeObserver(()=>{R.current=!1});return s.observe(l),(r=(p=document.fonts)==null?void 0:p.ready)==null||r.then(()=>{R.current=!1}),()=>s.disconnect()},[V,m]),h.useEffect(()=>{R.current=!1},[n,m]),h.useEffect(()=>{var s,p;if(!N||!m){$.current=0;return}function l(){const r=T.current;if(!r)return;const a=r.textContent??"",C=a.replace(/\s/g,"").length;if(C===0)return;const c=getComputedStyle(r),e=document.createElement("span");e.style.cssText="position:fixed;top:-9999px;left:-9999px;visibility:hidden;white-space:nowrap;pointer-events:none;",e.style.fontFamily=c.fontFamily,e.style.fontSize=c.fontSize,e.style.fontWeight=c.fontWeight,e.style.lineHeight=c.lineHeight,e.style.letterSpacing=c.letterSpacing,e.textContent=a,document.body.appendChild(e);const v=O=>{const M=[`'wght' ${O.toFixed(0)}`];for(const[f,x]of Object.entries(S))M.push(`'${f}' ${x}`);return M.join(", ")};e.style.fontVariationSettings=v(L);const i=e.scrollWidth;e.style.fontVariationSettings=v(d);const u=e.scrollWidth;document.body.removeChild(e),$.current=i>u?(i-u)/C:0}l(),(p=(s=document.fonts)==null?void 0:s.ready)==null||p.then(l)},[N,m,d,L,n,JSON.stringify(S)]);const Q=h.useMemo(()=>{if(!m)return n;A.current=[];let l=0;function s(p){if(typeof p=="string")return[...p].map(r=>{if(/\s/.test(r))return r;const a=l++;return W.jsx("span",{ref:C=>{A.current[a]=C},style:{fontVariationSettings:w(d)},children:r},a)});if(Array.isArray(p))return p.map((r,a)=>W.jsx(h.Fragment,{children:s(r)},a));if(h.isValidElement(p)){const r=p;if(r.props.children!==void 0)return h.cloneElement(r,{},s(r.props.children))}return p}return s(n)},[n,m,d,JSON.stringify(S)]);return W.jsx(o,{ref:B,className:E,style:{fontVariationSettings:w(d),...y},children:Q})});rt.displayName="MagnetChar";const pt=rt;exports.MAGNET_TYPE_CLASSES=z;exports.MagnetBlock=pt;exports.MagnetChar=rt;exports.MagnetTypeText=ft;exports.applyMagnetType=lt;exports.getCleanHTML=it;exports.removeMagnetType=mt;exports.startMagnetType=at;exports.useMagnetType=ut;
package/dist/index.d.ts CHANGED
@@ -38,9 +38,15 @@ export declare const MAGNET_TYPE_CLASSES: {
38
38
  readonly probe: "mt-probe";
39
39
  };
40
40
 
41
- export declare const MagnetBlock: default_2.ForwardRefExoticComponent<MagnetBlockProps & default_2.RefAttributes<HTMLElement>>;
41
+ /** @deprecated Use MagnetChar instead */
42
+ export declare const MagnetBlock: default_2.ForwardRefExoticComponent<MagnetCharProps & default_2.RefAttributes<HTMLElement>>;
42
43
 
43
- export declare interface MagnetBlockProps {
44
+ /** @deprecated Use MagnetCharProps instead */
45
+ export declare type MagnetBlockProps = MagnetCharProps;
46
+
47
+ export declare const MagnetChar: default_2.ForwardRefExoticComponent<MagnetCharProps & default_2.RefAttributes<HTMLElement>>;
48
+
49
+ export declare interface MagnetCharProps {
44
50
  children: default_2.ReactNode;
45
51
  /** HTML element to render. Default: 'p' */
46
52
  as?: default_2.ElementType;
@@ -87,7 +93,7 @@ export declare interface MagnetBlockProps {
87
93
  export declare type MagnetModeType = 'attract' | 'repel';
88
94
 
89
95
  /** Which mode magnetType operates in */
90
- export declare type MagnetTypeModeType = 'field' | 'legibility';
96
+ export declare type MagnetTypeModeType = 'word' | 'legibility';
91
97
 
92
98
  /**
93
99
  * Options for the magnetType effect.
@@ -98,9 +104,9 @@ export declare type MagnetTypeModeType = 'field' | 'legibility';
98
104
  */
99
105
  export declare interface MagnetTypeOptions {
100
106
  /**
101
- * Operating mode. Default: 'field'
107
+ * Operating mode. Default: 'word'
102
108
  *
103
- * - **'field'** — cursor proximity drives per-word font-variation-settings.
109
+ * - **'word'** — cursor proximity drives per-word font-variation-settings.
104
110
  * - **'legibility'** — cursor-driven wdth boost for confusable characters; both return a stop function.
105
111
  */
106
112
  mode?: MagnetTypeModeType;
@@ -249,13 +255,13 @@ export declare function startMagnetType(element: HTMLElement, originalHTML: stri
249
255
  /**
250
256
  * React hook that applies the magnetType effect to a ref'd element.
251
257
  *
252
- * For mode: 'field' — starts the cursor proximity rAF loop via startMagnetType.
258
+ * For mode: 'word' — starts the cursor proximity rAF loop via startMagnetType.
253
259
  * For mode: 'legibility' — starts the cursor-driven wdth boost via applyMagnetType.
254
260
  *
255
261
  * Both modes return a stop function on mount and restart when options change.
256
262
  * No ResizeObserver needed — the rAF loop reads live getBoundingClientRect each frame.
257
263
  *
258
- * Defaults to 'field' mode if mode is undefined.
264
+ * Defaults to 'word' mode if mode is undefined.
259
265
  */
260
266
  export declare function useMagnetType(options: MagnetTypeOptions): RefObject<HTMLElement | null>;
261
267
 
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import rt, { useRef as I, useCallback as z, useLayoutEffect as ft, useEffect as W, forwardRef as lt, useMemo as dt } from "react";
2
+ import rt, { useRef as I, useCallback as z, useLayoutEffect as dt, useEffect as W, forwardRef as lt, useMemo as mt } from "react";
3
3
  import { jsx as tt } from "react/jsx-runtime";
4
4
  const it = {
5
5
  i: 3,
@@ -62,7 +62,7 @@ function at(t, n, o) {
62
62
  function ot(t, n) {
63
63
  n.opacity !== void 0 && (t.style.opacity = String(n.opacity[0])), n.italic === !0 && (t.style.fontStyle = "");
64
64
  }
65
- function mt(t) {
65
+ function pt(t) {
66
66
  const n = t.cloneNode(!0), o = n.querySelectorAll(
67
67
  `.${nt.word}, .${nt.char}`
68
68
  );
@@ -77,7 +77,7 @@ function mt(t) {
77
77
  function Mt(t, n) {
78
78
  t.innerHTML = n;
79
79
  }
80
- function pt(t, n, o = {}) {
80
+ function ht(t, n, o = {}) {
81
81
  var p, r;
82
82
  if (typeof window > "u") return () => {
83
83
  };
@@ -86,8 +86,8 @@ function pt(t, n, o = {}) {
86
86
  };
87
87
  const M = o.wdthBoost ?? U.wdthBoost, h = o.radius ?? U.radius, d = o.falloff ?? U.falloff, b = o.scope ?? U.scope, F = o.props, m = o.transitionMs ?? 0, w = o.cachePositions ?? !0, L = window.scrollY;
88
88
  t.innerHTML = n;
89
- const X = getComputedStyle(t).fontVariationSettings, V = X.match(/"wdth"\s+([\d.eE+-]+)/), j = V ? parseFloat(V[1]) : 100, x = ct(t), y = [];
90
- for (const a of x) {
89
+ const X = getComputedStyle(t).fontVariationSettings, V = X.match(/"wdth"\s+([\d.eE+-]+)/), j = V ? parseFloat(V[1]) : 100, C = ct(t), y = [];
90
+ for (const a of C) {
91
91
  const E = a.textContent ?? "";
92
92
  if (!E || !E.split("").some((g) => g in it)) continue;
93
93
  const e = document.createDocumentFragment();
@@ -146,8 +146,8 @@ function pt(t, n, o = {}) {
146
146
  const P = c[i];
147
147
  u = P.left + P.width / 2, O = P.top + P.height / 2;
148
148
  }
149
- const S = Math.sqrt((a - u) ** 2 + (E - O) ** 2), f = Math.max(0, 1 - S / h), C = d === "quadratic" ? f * f : f, q = M * (g / 3) * C;
150
- e.style.fontVariationSettings = et(X, "wdth", j + q), F && at(e, F, C);
149
+ const S = Math.sqrt((a - u) ** 2 + (E - O) ** 2), f = Math.max(0, 1 - S / h), x = d === "quadratic" ? f * f : f, q = M * (g / 3) * x;
150
+ e.style.fontVariationSettings = et(X, "wdth", j + q), F && at(e, F, x);
151
151
  }), v = requestAnimationFrame(D);
152
152
  }
153
153
  function k(a) {
@@ -167,7 +167,7 @@ function pt(t, n, o = {}) {
167
167
  R = !1, cancelAnimationFrame(v), T !== null && clearTimeout(T), Y && Y.disconnect(), s.removeEventListener("mousemove", k), s.removeEventListener("mouseleave", J), s.removeEventListener("touchmove", Q), s.removeEventListener("touchend", l), t.innerHTML = n;
168
168
  };
169
169
  }
170
- function ht(t, n, o = {}) {
170
+ function yt(t, n, o = {}) {
171
171
  var a, E;
172
172
  if (typeof window > "u") return () => {
173
173
  };
@@ -176,7 +176,7 @@ function ht(t, n, o = {}) {
176
176
  };
177
177
  const M = o.axes ?? U.axes, h = o.radius ?? U.radius, d = o.falloff ?? U.falloff, b = o.magnetMode ?? U.magnetMode, F = o.scope ?? U.scope, m = o.props, w = o.transitionMs ?? 0, L = o.cachePositions ?? !0, X = o.stabilizeLayout ?? !0, V = window.scrollY;
178
178
  t.innerHTML = n;
179
- const j = ct(t), x = [];
179
+ const j = ct(t), C = [];
180
180
  for (const c of j) {
181
181
  const e = c.textContent ?? "";
182
182
  if (!e.trim()) continue;
@@ -184,14 +184,14 @@ function ht(t, n, o = {}) {
184
184
  for (let u = 0; u < g.length; u += 2) {
185
185
  const O = g[u], S = g[u + 1];
186
186
  if (!S) continue;
187
- const C = g[u + 3] === void 0 ? g[u + 2] ?? "" : "", q = document.createElement("span");
188
- q.className = nt.word, q.textContent = O + S + C, i.appendChild(q), x.push(q);
187
+ const x = g[u + 3] === void 0 ? g[u + 2] ?? "" : "", q = document.createElement("span");
188
+ q.className = nt.word, q.textContent = O + S + x, i.appendChild(q), C.push(q);
189
189
  }
190
190
  c.parentNode.replaceChild(i, c);
191
191
  }
192
192
  if (requestAnimationFrame(() => {
193
193
  typeof window < "u" && Math.abs(window.scrollY - V) > 2 && window.scrollTo({ top: V, behavior: "instant" });
194
- }), x.length === 0) return () => {
194
+ }), C.length === 0) return () => {
195
195
  };
196
196
  const y = getComputedStyle(t).fontVariationSettings, N = st(
197
197
  y,
@@ -201,29 +201,29 @@ function ht(t, n, o = {}) {
201
201
  if (X) {
202
202
  const c = st(
203
203
  y,
204
- Object.fromEntries(Object.entries(M).map(([f, [, C]]) => [f, C]))
204
+ Object.fromEntries(Object.entries(M).map(([f, [, x]]) => [f, x]))
205
205
  ), e = t.style.fontVariationSettings, g = t.style.whiteSpace, i = t.style.overflow;
206
206
  t.style.whiteSpace = "nowrap", t.style.overflow = "visible", t.style.fontVariationSettings = c;
207
207
  const u = t.scrollWidth;
208
208
  t.style.fontVariationSettings = N;
209
209
  const O = t.scrollWidth;
210
210
  t.style.fontVariationSettings = e, t.style.whiteSpace = g, t.style.overflow = i;
211
- const S = x.reduce(
212
- (f, C) => {
211
+ const S = C.reduce(
212
+ (f, x) => {
213
213
  var q;
214
- return f + (((q = C.textContent) == null ? void 0 : q.replace(/\s+/g, "").length) ?? 0);
214
+ return f + (((q = x.textContent) == null ? void 0 : q.replace(/\s+/g, "").length) ?? 0);
215
215
  },
216
216
  0
217
217
  );
218
218
  S > 0 && u > O && (H = (u - O) / S);
219
219
  }
220
- x.forEach((c) => {
220
+ C.forEach((c) => {
221
221
  c.style.fontVariationSettings = N, m && ot(c, m);
222
222
  });
223
223
  let _ = [], Y = !1;
224
224
  function A() {
225
225
  const c = window.scrollX, e = window.scrollY;
226
- _ = x.map((g) => {
226
+ _ = C.map((g) => {
227
227
  const i = g.getBoundingClientRect();
228
228
  return { cx: (i.left + i.right) / 2 + c, cy: (i.top + i.bottom) / 2 + e };
229
229
  }), Y = !0;
@@ -238,18 +238,18 @@ function ht(t, n, o = {}) {
238
238
  function J() {
239
239
  if (!D) return;
240
240
  if (!R) {
241
- x.forEach((i) => {
241
+ C.forEach((i) => {
242
242
  w > 0 && (i.style.transition = `font-variation-settings ${w}ms ease`), i.style.fontVariationSettings = N, X && (i.style.letterSpacing = ""), m && ot(i, m);
243
243
  }), w > 0 && (k !== null && clearTimeout(k), k = setTimeout(() => {
244
- x.forEach((i) => {
244
+ C.forEach((i) => {
245
245
  i.style.transition = "";
246
246
  }), k = null;
247
247
  }, w)), T = 0;
248
248
  return;
249
249
  }
250
250
  L && !Y && A();
251
- const c = L ? B + window.scrollX : B, e = L ? v + window.scrollY : v, g = L ? null : x.map((i) => i.getBoundingClientRect());
252
- x.forEach((i, u) => {
251
+ const c = L ? B + window.scrollX : B, e = L ? v + window.scrollY : v, g = L ? null : C.map((i) => i.getBoundingClientRect());
252
+ C.forEach((i, u) => {
253
253
  i.style.transition = "";
254
254
  let O, S;
255
255
  if (L)
@@ -258,10 +258,10 @@ function ht(t, n, o = {}) {
258
258
  const K = g[u];
259
259
  O = K.left + K.width / 2, S = K.top + K.height / 2;
260
260
  }
261
- const f = Math.sqrt((c - O) ** 2 + (e - S) ** 2), C = Math.max(0, 1 - f / h), q = d === "quadratic" ? C * C : C, P = b === "repel" ? 1 - q : q, Z = {};
261
+ const f = Math.sqrt((c - O) ** 2 + (e - S) ** 2), x = Math.max(0, 1 - f / h), q = d === "quadratic" ? x * x : x, P = b === "repel" ? 1 - q : q, Z = {};
262
262
  for (const K of Object.keys(M)) {
263
- const [G, ut] = M[K] ?? [300, 500];
264
- Z[K] = G + (ut - G) * P;
263
+ const [G, ft] = M[K] ?? [300, 500];
264
+ Z[K] = G + (ft - G) * P;
265
265
  }
266
266
  i.style.fontVariationSettings = st(y, Z), X && H !== 0 && (i.style.letterSpacing = `${(-H * P).toFixed(3)}px`), m && at(i, m, q);
267
267
  }), T = requestAnimationFrame(J);
@@ -283,24 +283,24 @@ function ht(t, n, o = {}) {
283
283
  D = !1, cancelAnimationFrame(T), k !== null && clearTimeout(k), $ && $.disconnect(), r.removeEventListener("mousemove", Q), r.removeEventListener("mouseleave", l), r.removeEventListener("touchmove", s), r.removeEventListener("touchend", p), t.innerHTML = n;
284
284
  };
285
285
  }
286
- function yt(t) {
286
+ function gt(t) {
287
287
  const n = I(null), o = I(null), M = I(t);
288
288
  M.current = t;
289
- const h = I(null), d = t.mode ?? "field", { axes: b, radius: F, falloff: m, magnetMode: w, wdthBoost: L, scope: X } = t, V = b ? JSON.stringify(b) : void 0, j = t.props ? JSON.stringify(t.props) : void 0, x = z(() => {
289
+ const h = I(null), d = t.mode ?? "word", { axes: b, radius: F, falloff: m, magnetMode: w, wdthBoost: L, scope: X } = t, V = b ? JSON.stringify(b) : void 0, j = t.props ? JSON.stringify(t.props) : void 0, C = z(() => {
290
290
  const y = n.current;
291
291
  if (!y) return;
292
- o.current === null && (o.current = mt(y)), h.current && (h.current(), h.current = null), (M.current.mode ?? "field") === "field" ? h.current = ht(y, o.current, M.current) : h.current = pt(y, o.current, M.current);
292
+ o.current === null && (o.current = pt(y)), h.current && (h.current(), h.current = null), (M.current.mode ?? "word") === "word" ? h.current = yt(y, o.current, M.current) : h.current = ht(y, o.current, M.current);
293
293
  }, [d, V, F, m, w, L, X, j]);
294
- return ft(() => (x(), () => {
294
+ return dt(() => (C(), () => {
295
295
  h.current && (h.current(), h.current = null);
296
- }), [x]), W(() => {
296
+ }), [C]), W(() => {
297
297
  var y, N;
298
- (N = (y = document.fonts) == null ? void 0 : y.ready) == null || N.then(x);
299
- }, [x]), n;
298
+ (N = (y = document.fonts) == null ? void 0 : y.ready) == null || N.then(C);
299
+ }, [C]), n;
300
300
  }
301
- const gt = lt(
301
+ const vt = lt(
302
302
  function({ children: n, as: o = "p", className: M, style: h, ...d }, b) {
303
- const F = yt(d), m = z(
303
+ const F = gt(d), m = z(
304
304
  (w) => {
305
305
  F.current = w, typeof b == "function" ? b(w) : b && (b.current = w);
306
306
  },
@@ -310,8 +310,8 @@ const gt = lt(
310
310
  return /* @__PURE__ */ tt(o, { ref: m, className: M, style: h, children: n });
311
311
  }
312
312
  );
313
- gt.displayName = "MagnetTypeText";
314
- const vt = lt(
313
+ vt.displayName = "MagnetTypeText";
314
+ const ut = lt(
315
315
  function({
316
316
  children: n,
317
317
  as: o = "p",
@@ -326,9 +326,9 @@ const vt = lt(
326
326
  rafThrottle: X = !0,
327
327
  stabilizeLayout: V = !0
328
328
  }, j) {
329
- const x = I(null), y = I(null), N = I([]), H = I([]), _ = I(null), Y = I(!1), A = I(null), $ = I(0), B = z(
329
+ const C = I(null), y = I(null), N = I([]), H = I([]), _ = I(null), Y = I(!1), A = I(null), $ = I(0), B = z(
330
330
  (l) => {
331
- x.current = l, typeof j == "function" ? j(l) : j && (j.current = l);
331
+ C.current = l, typeof j == "function" ? j(l) : j && (j.current = l);
332
332
  },
333
333
  // eslint-disable-next-line react-hooks/exhaustive-deps
334
334
  [j]
@@ -339,7 +339,7 @@ const vt = lt(
339
339
  return s.join(", ");
340
340
  }
341
341
  function R() {
342
- const l = x.current;
342
+ const l = C.current;
343
343
  if (!l) return;
344
344
  const s = window.scrollX, p = window.scrollY, r = l.getBoundingClientRect();
345
345
  _.current = {
@@ -357,7 +357,7 @@ const vt = lt(
357
357
  }), Y.current = !0;
358
358
  }
359
359
  function T(l, s) {
360
- const p = x.current;
360
+ const p = C.current;
361
361
  if (!p) return;
362
362
  L && !Y.current && R();
363
363
  let r, a, E, c, e, g;
@@ -388,15 +388,15 @@ const vt = lt(
388
388
  const S = N.current;
389
389
  if (L && H.current.length === S.length)
390
390
  for (let f = 0; f < S.length; f++) {
391
- const C = S[f];
392
- if (!C) continue;
391
+ const x = S[f];
392
+ if (!x) continue;
393
393
  const { cx: q, cy: P } = H.current[f], Z = Math.sqrt((e - q) ** 2 + (g - P) ** 2), G = 1 - (1 - Math.max(0, 1 - Z / m)) ** 2;
394
- C.style.fontVariationSettings = v(d + (b - d) * G), V && $.current !== 0 && (C.style.letterSpacing = `${(-$.current * G).toFixed(3)}px`);
394
+ x.style.fontVariationSettings = v(d + (b - d) * G), V && $.current !== 0 && (x.style.letterSpacing = `${(-$.current * G).toFixed(3)}px`);
395
395
  }
396
396
  else
397
397
  for (const f of S) {
398
398
  if (!f) continue;
399
- const C = f.getBoundingClientRect(), q = (C.left + C.right) / 2, P = (C.top + C.bottom) / 2, Z = Math.sqrt((l - q) ** 2 + (s - P) ** 2), G = 1 - (1 - Math.max(0, 1 - Z / m)) ** 2;
399
+ const x = f.getBoundingClientRect(), q = (x.left + x.right) / 2, P = (x.top + x.bottom) / 2, Z = Math.sqrt((l - q) ** 2 + (s - P) ** 2), G = 1 - (1 - Math.max(0, 1 - Z / m)) ** 2;
400
400
  f.style.fontVariationSettings = v(d + (b - d) * G), V && $.current !== 0 && (f.style.letterSpacing = `${(-$.current * G).toFixed(3)}px`);
401
401
  }
402
402
  }
@@ -418,7 +418,7 @@ const vt = lt(
418
418
  ), J = z(
419
419
  () => {
420
420
  y.current = null, A.current !== null && (cancelAnimationFrame(A.current), A.current = null);
421
- const l = x.current;
421
+ const l = C.current;
422
422
  l && (l.style.fontVariationSettings = v(d));
423
423
  for (const s of N.current)
424
424
  s && (s.style.fontVariationSettings = v(d), V && (s.style.letterSpacing = ""));
@@ -432,7 +432,7 @@ const vt = lt(
432
432
  var p, r;
433
433
  if (!L || !m) return;
434
434
  Y.current = !1;
435
- const l = x.current;
435
+ const l = C.current;
436
436
  if (!l) return;
437
437
  const s = new ResizeObserver(() => {
438
438
  Y.current = !1;
@@ -449,7 +449,7 @@ const vt = lt(
449
449
  return;
450
450
  }
451
451
  function l() {
452
- const r = x.current;
452
+ const r = C.current;
453
453
  if (!r) return;
454
454
  const a = r.textContent ?? "", E = a.replace(/\s/g, "").length;
455
455
  if (E === 0) return;
@@ -457,7 +457,7 @@ const vt = lt(
457
457
  e.style.cssText = "position:fixed;top:-9999px;left:-9999px;visibility:hidden;white-space:nowrap;pointer-events:none;", e.style.fontFamily = c.fontFamily, e.style.fontSize = c.fontSize, e.style.fontWeight = c.fontWeight, e.style.lineHeight = c.lineHeight, e.style.letterSpacing = c.letterSpacing, e.textContent = a, document.body.appendChild(e);
458
458
  const g = (O) => {
459
459
  const S = [`'wght' ${O.toFixed(0)}`];
460
- for (const [f, C] of Object.entries(w)) S.push(`'${f}' ${C}`);
460
+ for (const [f, x] of Object.entries(w)) S.push(`'${f}' ${x}`);
461
461
  return S.join(", ");
462
462
  };
463
463
  e.style.fontVariationSettings = g(b);
@@ -468,7 +468,7 @@ const vt = lt(
468
468
  }
469
469
  l(), (p = (s = document.fonts) == null ? void 0 : s.ready) == null || p.then(l);
470
470
  }, [V, m, d, b, n, JSON.stringify(w)]);
471
- const Q = dt(() => {
471
+ const Q = mt(() => {
472
472
  if (!m) return n;
473
473
  N.current = [];
474
474
  let l = 0;
@@ -510,14 +510,16 @@ const vt = lt(
510
510
  );
511
511
  }
512
512
  );
513
- vt.displayName = "MagnetBlock";
513
+ ut.displayName = "MagnetChar";
514
+ const Et = ut;
514
515
  export {
515
516
  nt as MAGNET_TYPE_CLASSES,
516
- vt as MagnetBlock,
517
- gt as MagnetTypeText,
518
- pt as applyMagnetType,
519
- mt as getCleanHTML,
517
+ Et as MagnetBlock,
518
+ ut as MagnetChar,
519
+ vt as MagnetTypeText,
520
+ ht as applyMagnetType,
521
+ pt as getCleanHTML,
520
522
  Mt as removeMagnetType,
521
- ht as startMagnetType,
522
- yt as useMagnetType
523
+ yt as startMagnetType,
524
+ gt as useMagnetType
523
525
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liiift-studio/magnettype",
3
- "version": "1.1.6",
3
+ "version": "1.2.0",
4
4
  "description": "Cursor-field per-word variable font axis variation and per-character legibility mode",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",