@liiift-studio/magnettype 1.0.8 → 1.1.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
@@ -140,6 +140,7 @@ const legibilityOpts: MagnetTypeOptions = {
140
140
  | `falloff` | `'quadratic'` | *(field mode only)* Falloff curve. `'linear'` — strength decreases linearly with distance. `'quadratic'` — strength decreases as distance², giving a tighter hot zone and a sharper peak feel |
141
141
  | `magnetMode` | `'attract'` | *(field mode only)* `'attract'` — words near the cursor approach `peakValue`. `'repel'` — words near the cursor stay at `restValue`; words farther away approach `peakValue` |
142
142
  | `wdthBoost` | `6` | *(legibility mode only)* `wdth` axis units added to confusable characters, scaled by risk level. Risk 3 characters (`i l 1 I`) receive the full boost; risk 2 characters (`r 0 O`) receive ⅔; risk 1 characters (`n m o b d p q c e`) receive ⅓ |
143
+ | `transitionMs` | `0` | Duration in milliseconds for the CSS transition back to rest values when the cursor leaves (or a touch ends). `0` preserves the existing instant snap. When > 0, `font-variation-settings` animates back over the given duration using `ease` easing. The transition is cleared immediately on the next `mousemove`/`touchmove` so live tracking is not delayed |
143
144
  | `as` | `'p'` | HTML element to render, e.g. `'h1'`, `'div'`, `'span'`. Accepts any valid React element type. *(React component only)* |
144
145
 
145
146
  ---
@@ -187,12 +188,10 @@ The package itself has zero runtime dependencies. Do not remove this entry.
187
188
 
188
189
  ## Future improvements
189
190
 
190
- - **Touch support** — `touchmove` events for mobile field mode; currently `mousemove`-only, so the effect is desktop-exclusive
191
- - **Smooth transition on stop** — animate axis values back to `restValue` over a short duration when the cursor leaves, rather than snapping on the next rAF tick
192
191
  - **Custom confusable table** — allow callers to pass their own `Record<string, number>` to override or extend the built-in character risk map for language- or font-specific tuning
193
192
  - **Axis clamping** — optional per-axis min/max clamp to prevent axis values from exceeding a font's supported range, avoiding undefined browser rendering behaviour
194
193
  - **SSR hydration** — pre-render legibility mode markup on the server so boosted characters are present from first paint without a client-side flash
195
194
 
196
195
  ---
197
196
 
198
- Current version: v1.0.7
197
+ Current version: 0.1.3
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const C=require("react"),z=require("react/jsx-runtime"),_={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},B={word:"mt-word",char:"mt-char",probe:"mt-probe"},L={axes:{wght:[300,500]},radius:120,falloff:"quadratic",magnetMode:"attract",wdthBoost:6,scope:"document"};function k(e,t=[]){return e.nodeType===Node.TEXT_NODE?t.push(e):e.childNodes.forEach(n=>k(n,t)),t}function j(e,t,n){if(!e||e==="normal")return`"${t}" ${n}`;const c=new RegExp(`(["'])${t}\\1\\s+[\\d.eE+-]+`),o=`"${t}" ${n}`;return c.test(e)?e.replace(c,o):`${e}, ${o}`}function D(e,t){let n=e;for(const[c,o]of Object.entries(t))n=j(n,c,o);return n}function I(e,t,n){if(t.opacity!==void 0){const[c,o]=t.opacity;e.style.opacity=String(c+(o-c)*n)}t.italic===!0&&(e.style.fontStyle=n>.5?"italic":"")}function V(e,t){t.opacity!==void 0&&(e.style.opacity=String(t.opacity[0])),t.italic===!0&&(e.style.fontStyle="")}function P(e){const t=e.cloneNode(!0),n=t.querySelectorAll(`.${B.word}, .${B.char}`);return Array.from(n).reverse().forEach(o=>{const y=o.parentNode;if(y){for(;o.firstChild;)y.insertBefore(o.firstChild,o);y.removeChild(o)}}),t.innerHTML}function Q(e,t){e.innerHTML=t}function W(e,t,n={}){if(typeof window>"u")return()=>{};if(window.matchMedia("(prefers-reduced-motion: reduce)").matches)return e.innerHTML=t,()=>{};const c=n.wdthBoost??L.wdthBoost,o=n.radius??L.radius,y=n.falloff??L.falloff,m=n.scope??L.scope,p=n.props,d=window.scrollY;e.innerHTML=t;const l=getComputedStyle(e).fontVariationSettings,b=l.match(/"wdth"\s+([\d.eE+-]+)/),h=b?parseFloat(b[1]):100,q=k(e),g=[];for(const r of q){const s=r.textContent??"";if(!s||!s.split("").some(i=>i in _))continue;const u=document.createDocumentFragment();for(const i of s){const N=_[i];if(N===void 0){const a=u.lastChild;a&&a.nodeType===Node.TEXT_NODE?a.textContent+=i:u.appendChild(document.createTextNode(i))}else{const a=document.createElement("span");a.className=B.char,a.style.fontVariationSettings=j(l,"wdth",h),a.textContent=i,u.appendChild(a),g.push({span:a,riskLevel:N})}}r.parentNode.replaceChild(u,r)}if(requestAnimationFrame(()=>{typeof window<"u"&&Math.abs(window.scrollY-d)>2&&window.scrollTo({top:d,behavior:"instant"})}),g.length===0)return()=>{};p&&g.forEach(({span:r})=>V(r,p));let w=-9999,v=-9999,M=!1,f=0,R=!0;function A(){if(!R)return;if(!M){g.forEach(({span:s})=>{s.style.fontVariationSettings=j(l,"wdth",h),p&&V(s,p)}),f=0;return}const r=g.map(({span:s})=>s.getBoundingClientRect());g.forEach(({span:s,riskLevel:T},u)=>{const i=r[u],N=i.left+i.width/2,a=i.top+i.height/2,F=Math.sqrt((w-N)**2+(v-a)**2),x=Math.max(0,1-F/o),S=y==="quadratic"?x*x:x,$=c*(T/3)*S;s.style.fontVariationSettings=j(l,"wdth",h+$),p&&I(s,p,S)}),f=requestAnimationFrame(A)}function O(r){w=r.clientX,v=r.clientY,M||(M=!0),f===0&&(f=requestAnimationFrame(A))}function Y(){M=!1,f===0&&(f=requestAnimationFrame(A))}const E=m==="document"?document:e;return E.addEventListener("mousemove",O),E.addEventListener("mouseleave",Y),()=>{R=!1,cancelAnimationFrame(f),E.removeEventListener("mousemove",O),E.removeEventListener("mouseleave",Y),e.innerHTML=t}}function G(e,t,n={}){if(typeof window>"u")return()=>{};if(window.matchMedia("(prefers-reduced-motion: reduce)").matches)return e.innerHTML=t,()=>{};const c=n.axes??L.axes,o=n.radius??L.radius,y=n.falloff??L.falloff,m=n.magnetMode??L.magnetMode,p=n.scope??L.scope,d=n.props,l=window.scrollY;e.innerHTML=t;const b=k(e),h=[];for(const r of b){const s=r.textContent??"";if(!s.trim())continue;const T=s.split(/(\S+)/),u=document.createDocumentFragment();for(let i=0;i<T.length;i+=2){const N=T[i],a=T[i+1];if(!a)continue;const x=T[i+3]===void 0?T[i+2]??"":"",S=document.createElement("span");S.className=B.word,S.textContent=N+a+x,u.appendChild(S),h.push(S)}r.parentNode.replaceChild(u,r)}if(requestAnimationFrame(()=>{typeof window<"u"&&Math.abs(window.scrollY-l)>2&&window.scrollTo({top:l,behavior:"instant"})}),h.length===0)return()=>{};const q=getComputedStyle(e).fontVariationSettings,g=D(q,Object.fromEntries(Object.entries(c).map(([r,[s]])=>[r,s])));h.forEach(r=>{r.style.fontVariationSettings=g,d&&V(r,d)});let w=-9999,v=-9999,M=!1,f=0,R=!0;function A(){if(!R)return;if(!M){h.forEach(s=>{s.style.fontVariationSettings=g,d&&V(s,d)}),f=0;return}const r=h.map(s=>s.getBoundingClientRect());h.forEach((s,T)=>{const u=r[T],i=u.left+u.width/2,N=u.top+u.height/2,a=Math.sqrt((w-i)**2+(v-N)**2),F=Math.max(0,1-a/o),x=y==="quadratic"?F*F:F,S=m==="repel"?1-x:x,$={};for(const H of Object.keys(c)){const[X,U]=c[H]??[300,500];$[H]=X+(U-X)*S}s.style.fontVariationSettings=D(q,$),d&&I(s,d,x)}),f=requestAnimationFrame(A)}function O(r){w=r.clientX,v=r.clientY,M||(M=!0),f===0&&(f=requestAnimationFrame(A))}function Y(){M=!1,f===0&&(f=requestAnimationFrame(A))}const E=p==="document"?document:e;return E.addEventListener("mousemove",O),E.addEventListener("mouseleave",Y),()=>{R=!1,cancelAnimationFrame(f),E.removeEventListener("mousemove",O),E.removeEventListener("mouseleave",Y),e.innerHTML=t}}function J(e){const t=C.useRef(null),n=C.useRef(null),c=C.useRef(e);c.current=e;const o=C.useRef(null),y=e.mode??"field",{axes:m,radius:p,falloff:d,magnetMode:l,wdthBoost:b,scope:h}=e,q=m?JSON.stringify(m):void 0,g=e.props?JSON.stringify(e.props):void 0,w=C.useCallback(()=>{const v=t.current;if(!v)return;n.current===null&&(n.current=P(v)),o.current&&(o.current(),o.current=null),(c.current.mode??"field")==="field"?o.current=G(v,n.current,c.current):o.current=W(v,n.current,c.current)},[y,q,p,d,l,b,h,g]);return C.useLayoutEffect(()=>(w(),()=>{o.current&&(o.current(),o.current=null)}),[w]),C.useEffect(()=>{document.fonts.ready.then(w)},[w]),t}const K=C.forwardRef(function({children:t,as:n="p",className:c,style:o,...y},m){const p=J(y),d=C.useCallback(l=>{p.current=l,typeof m=="function"?m(l):m&&(m.current=l)},[m]);return z.jsx(n,{ref:d,className:c,style:o,children:t})});K.displayName="MagnetTypeText";exports.MAGNET_TYPE_CLASSES=B;exports.MagnetTypeText=K;exports.applyMagnetType=W;exports.getCleanHTML=P;exports.removeMagnetType=Q;exports.startMagnetType=G;exports.useMagnetType=J;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const M=require("react"),K=require("react/jsx-runtime"),G={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},R={word:"mt-word",char:"mt-char",probe:"mt-probe"},Y={axes:{wght:[300,500]},radius:120,falloff:"quadratic",magnetMode:"attract",wdthBoost:6,scope:"document"};function _(t,o=[]){return t.nodeType===Node.TEXT_NODE?o.push(t):t.childNodes.forEach(r=>_(r,o)),o}function H(t,o,r){if(!t||t==="normal")return`"${o}" ${r}`;const f=new RegExp(`(["'])${o}\\1\\s+[\\d.eE+-]+`),c=`"${o}" ${r}`;return f.test(t)?t.replace(f,c):`${t}, ${c}`}function J(t,o){let r=t;for(const[f,c]of Object.entries(o))r=H(r,f,c);return r}function U(t,o,r){if(o.opacity!==void 0){const[f,c]=o.opacity;t.style.opacity=String(f+(c-f)*r)}o.italic===!0&&(t.style.fontStyle=r>.5?"italic":"")}function P(t,o){o.opacity!==void 0&&(t.style.opacity=String(o.opacity[0])),o.italic===!0&&(t.style.fontStyle="")}function z(t){const o=t.cloneNode(!0),r=o.querySelectorAll(`.${R.word}, .${R.char}`);return Array.from(r).reverse().forEach(c=>{const u=c.parentNode;if(u){for(;c.firstChild;)u.insertBefore(c.firstChild,c);u.removeChild(c)}}),o.innerHTML}function ot(t,o){t.innerHTML=o}function Q(t,o,r={}){if(typeof window>"u")return()=>{};if(window.matchMedia("(prefers-reduced-motion: reduce)").matches)return t.innerHTML=o,()=>{};const f=r.wdthBoost??Y.wdthBoost,c=r.radius??Y.radius,u=r.falloff??Y.falloff,T=r.scope??Y.scope,v=r.props,m=r.transitionMs??0,S=window.scrollY;t.innerHTML=o;const L=getComputedStyle(t).fontVariationSettings,b=L.match(/"wdth"\s+([\d.eE+-]+)/),y=b?parseFloat(b[1]):100,A=_(t),w=[];for(const e of A){const n=e.textContent??"";if(!n||!n.split("").some(a=>a in G))continue;const h=document.createDocumentFragment();for(const a of n){const B=G[a];if(B===void 0){const E=h.lastChild;E&&E.nodeType===Node.TEXT_NODE?E.textContent+=a:h.appendChild(document.createTextNode(a))}else{const E=document.createElement("span");E.className=R.char,E.style.fontVariationSettings=H(L,"wdth",y),E.textContent=a,h.appendChild(E),w.push({span:E,riskLevel:B})}}e.parentNode.replaceChild(h,e)}if(requestAnimationFrame(()=>{typeof window<"u"&&Math.abs(window.scrollY-S)>2&&window.scrollTo({top:S,behavior:"instant"})}),w.length===0)return()=>{};v&&w.forEach(({span:e})=>P(e,v));let p=-9999,q=-9999,g=!1,i=0,V=!0,s=null;function d(){if(!V)return;if(!g){w.forEach(({span:n})=>{m>0&&(n.style.transition=`font-variation-settings ${m}ms ease`),n.style.fontVariationSettings=H(L,"wdth",y),v&&P(n,v)}),m>0&&(s!==null&&clearTimeout(s),s=setTimeout(()=>{w.forEach(({span:n})=>{n.style.transition=""}),s=null},m)),i=0;return}const e=w.map(({span:n})=>n.getBoundingClientRect());w.forEach(({span:n,riskLevel:x},h)=>{n.style.transition="";const a=e[h],B=a.left+a.width/2,E=a.top+a.height/2,j=Math.sqrt((p-B)**2+(q-E)**2),O=Math.max(0,1-j/c),$=u==="quadratic"?O*O:O,X=f*(x/3)*$;n.style.fontVariationSettings=H(L,"wdth",y+X),v&&U(n,v,$)}),i=requestAnimationFrame(d)}function N(e){p=e.clientX,q=e.clientY,g||(g=!0),i===0&&(i=requestAnimationFrame(d))}function C(){g=!1,i===0&&(i=requestAnimationFrame(d))}function F(e){e.touches.length!==0&&(p=e.touches[0].clientX,q=e.touches[0].clientY,g||(g=!0),i===0&&(i=requestAnimationFrame(d)))}function k(){g=!1,i===0&&(i=requestAnimationFrame(d))}const l=T==="document"?document:t;return l.addEventListener("mousemove",N),l.addEventListener("mouseleave",C),l.addEventListener("touchmove",F,{passive:!0}),l.addEventListener("touchend",k),()=>{V=!1,cancelAnimationFrame(i),s!==null&&clearTimeout(s),l.removeEventListener("mousemove",N),l.removeEventListener("mouseleave",C),l.removeEventListener("touchmove",F),l.removeEventListener("touchend",k),t.innerHTML=o}}function Z(t,o,r={}){if(typeof window>"u")return()=>{};if(window.matchMedia("(prefers-reduced-motion: reduce)").matches)return t.innerHTML=o,()=>{};const f=r.axes??Y.axes,c=r.radius??Y.radius,u=r.falloff??Y.falloff,T=r.magnetMode??Y.magnetMode,v=r.scope??Y.scope,m=r.props,S=r.transitionMs??0,L=window.scrollY;t.innerHTML=o;const b=_(t),y=[];for(const e of b){const n=e.textContent??"";if(!n.trim())continue;const x=n.split(/(\S+)/),h=document.createDocumentFragment();for(let a=0;a<x.length;a+=2){const B=x[a],E=x[a+1];if(!E)continue;const O=x[a+3]===void 0?x[a+2]??"":"",$=document.createElement("span");$.className=R.word,$.textContent=B+E+O,h.appendChild($),y.push($)}e.parentNode.replaceChild(h,e)}if(requestAnimationFrame(()=>{typeof window<"u"&&Math.abs(window.scrollY-L)>2&&window.scrollTo({top:L,behavior:"instant"})}),y.length===0)return()=>{};const A=getComputedStyle(t).fontVariationSettings,w=J(A,Object.fromEntries(Object.entries(f).map(([e,[n]])=>[e,n])));y.forEach(e=>{e.style.fontVariationSettings=w,m&&P(e,m)});let p=-9999,q=-9999,g=!1,i=0,V=!0,s=null;function d(){if(!V)return;if(!g){y.forEach(n=>{S>0&&(n.style.transition=`font-variation-settings ${S}ms ease`),n.style.fontVariationSettings=w,m&&P(n,m)}),S>0&&(s!==null&&clearTimeout(s),s=setTimeout(()=>{y.forEach(n=>{n.style.transition=""}),s=null},S)),i=0;return}const e=y.map(n=>n.getBoundingClientRect());y.forEach((n,x)=>{n.style.transition="";const h=e[x],a=h.left+h.width/2,B=h.top+h.height/2,E=Math.sqrt((p-a)**2+(q-B)**2),j=Math.max(0,1-E/c),O=u==="quadratic"?j*j:j,$=T==="repel"?1-O:O,X={};for(const D of Object.keys(f)){const[I,nt]=f[D]??[300,500];X[D]=I+(nt-I)*$}n.style.fontVariationSettings=J(A,X),m&&U(n,m,O)}),i=requestAnimationFrame(d)}function N(e){p=e.clientX,q=e.clientY,g||(g=!0),i===0&&(i=requestAnimationFrame(d))}function C(){g=!1,i===0&&(i=requestAnimationFrame(d))}function F(e){e.touches.length!==0&&(p=e.touches[0].clientX,q=e.touches[0].clientY,g||(g=!0),i===0&&(i=requestAnimationFrame(d)))}function k(){g=!1,i===0&&(i=requestAnimationFrame(d))}const l=v==="document"?document:t;return l.addEventListener("mousemove",N),l.addEventListener("mouseleave",C),l.addEventListener("touchmove",F,{passive:!0}),l.addEventListener("touchend",k),()=>{V=!1,cancelAnimationFrame(i),s!==null&&clearTimeout(s),l.removeEventListener("mousemove",N),l.removeEventListener("mouseleave",C),l.removeEventListener("touchmove",F),l.removeEventListener("touchend",k),t.innerHTML=o}}function W(t){const o=M.useRef(null),r=M.useRef(null),f=M.useRef(t);f.current=t;const c=M.useRef(null),u=t.mode??"field",{axes:T,radius:v,falloff:m,magnetMode:S,wdthBoost:L,scope:b}=t,y=T?JSON.stringify(T):void 0,A=t.props?JSON.stringify(t.props):void 0,w=M.useCallback(()=>{const p=o.current;if(!p)return;r.current===null&&(r.current=z(p)),c.current&&(c.current(),c.current=null),(f.current.mode??"field")==="field"?c.current=Z(p,r.current,f.current):c.current=Q(p,r.current,f.current)},[u,y,v,m,S,L,b,A]);return M.useLayoutEffect(()=>(w(),()=>{c.current&&(c.current(),c.current=null)}),[w]),M.useEffect(()=>{document.fonts.ready.then(w)},[w]),o}const tt=M.forwardRef(function({children:o,as:r="p",className:f,style:c,...u},T){const v=W(u),m=M.useCallback(S=>{v.current=S,typeof T=="function"?T(S):T&&(T.current=S)},[T]);return K.jsx(r,{ref:m,className:f,style:c,children:o})});tt.displayName="MagnetTypeText";const et=M.forwardRef(function({children:o,as:r="p",className:f,style:c,minWeight:u=300,maxWeight:T=600,proximityRadius:v,spreadRadius:m,fixedAxes:S={}},L){const b=M.useRef(null),y=M.useRef(null),A=M.useRef([]),w=M.useCallback(s=>{b.current=s,typeof L=="function"?L(s):L&&(L.current=s)},[L]);function p(s){const d=[`'wght' ${s.toFixed(0)}`];for(const[N,C]of Object.entries(S))d.push(`'${N}' ${C}`);return d.join(", ")}M.useEffect(()=>{var k;if(!m)return;const s=b.current;if(!s)return;const d=[],N=document.createTreeWalker(s,NodeFilter.SHOW_TEXT),C=[];let F;for(;F=N.nextNode();)C.push(F);for(const l of C){const e=l.textContent??"";if(!/\S/.test(e))continue;const n=e.split(/(\s+)/),x=document.createDocumentFragment();for(const h of n)if(h!=="")if(/^\s+$/.test(h))x.appendChild(document.createTextNode(h));else{const a=document.createElement("span");a.style.fontVariationSettings=p(u),a.textContent=h,d.push(a),x.appendChild(a)}(k=l.parentNode)==null||k.replaceChild(x,l)}A.current=d},[]);function q(s,d){const N=b.current;if(!N)return;const C=N.getBoundingClientRect(),F=Math.max(C.left-s,0,s-C.right),k=Math.max(C.top-d,0,d-C.bottom),l=Math.sqrt(F*F+k*k);if(v!==void 0&&!m){const n=1-(1-Math.max(0,1-l/v))**2;N.style.fontVariationSettings=p(u+(T-u)*n);return}if(m){if(v!==void 0&&l>v){N.style.fontVariationSettings=p(u);for(const e of A.current)e.style.fontVariationSettings=p(u);return}for(const e of A.current){const n=e.getBoundingClientRect(),x=(n.left+n.right)/2,h=(n.top+n.bottom)/2,a=Math.sqrt((s-x)**2+(d-h)**2),E=1-(1-Math.max(0,1-a/m))**2;e.style.fontVariationSettings=p(u+(T-u)*E)}}}const g=M.useCallback(s=>{y.current={x:s.clientX,y:s.clientY},q(s.clientX,s.clientY)},[u,T,v,m]),i=M.useCallback(()=>{y.current&&q(y.current.x,y.current.y)},[u,T,v,m]),V=M.useCallback(()=>{y.current=null;const s=b.current;s&&(s.style.fontVariationSettings=p(u));for(const d of A.current)d.style.fontVariationSettings=p(u)},[u]);return M.useEffect(()=>(window.addEventListener("mousemove",g,{passive:!0}),window.addEventListener("scroll",i,{passive:!0,capture:!0}),document.documentElement.addEventListener("mouseleave",V),()=>{window.removeEventListener("mousemove",g),window.removeEventListener("scroll",i,{capture:!0}),document.documentElement.removeEventListener("mouseleave",V)}),[g,i,V]),K.jsx(r,{ref:w,className:f,style:{fontVariationSettings:p(u),...c},children:o})});et.displayName="MagnetBlock";exports.MAGNET_TYPE_CLASSES=R;exports.MagnetBlock=et;exports.MagnetTypeText=tt;exports.applyMagnetType=Q;exports.getCleanHTML=z;exports.removeMagnetType=ot;exports.startMagnetType=Z;exports.useMagnetType=W;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { default as default_2 } from 'react';
2
+ import { ForwardRefExoticComponent } from 'react';
3
+ import { RefAttributes } from 'react';
2
4
  import { RefObject } from 'react';
3
5
 
4
6
  /**
@@ -38,6 +40,28 @@ export declare const MAGNET_TYPE_CLASSES: {
38
40
  readonly probe: "mt-probe";
39
41
  };
40
42
 
43
+ /**
44
+ * Drop-in block element with cursor-proximity variable font weight variation.
45
+ * Accepts any ReactNode — use spreadRadius for per-word spread, proximityRadius
46
+ * for a whole-element proximity gate, or combine both.
47
+ */
48
+ export declare const MagnetBlock: ForwardRefExoticComponent<MagnetBlockProps & RefAttributes<HTMLElement>>;
49
+
50
+ export declare interface MagnetBlockProps {
51
+ children: React.ReactNode;
52
+ /** HTML element to render. Default: 'p' */
53
+ as?: React.ElementType;
54
+ className?: string;
55
+ style?: React.CSSProperties;
56
+ minWeight?: number;
57
+ maxWeight?: number;
58
+ /** Pixel distance from the element edge within which the whole-element effect activates */
59
+ proximityRadius?: number;
60
+ /** Pixel distance from the cursor within which each word's weight rises to max */
61
+ spreadRadius?: number;
62
+ fixedAxes?: Record<string, number>;
63
+ }
64
+
41
65
  /** Whether cursor proximity attracts toward peak or repels toward rest */
42
66
  export declare type MagnetModeType = 'attract' | 'repel';
43
67
 
@@ -104,6 +128,15 @@ export declare interface MagnetTypeOptions {
104
128
  * Risk 3 characters receive wdthBoost × (3/3) = full boost at peak.
105
129
  */
106
130
  wdthBoost?: number;
131
+ /**
132
+ * Duration in milliseconds for the CSS transition back to rest values when the
133
+ * cursor leaves (mouseleave / touchend). Default: 0 (instant snap, no transition).
134
+ *
135
+ * When > 0, sets `transition: font-variation-settings <transitionMs>ms ease` on
136
+ * each span at leave time, then removes the transition property after the duration.
137
+ * On mousemove / touchmove the transition is cleared so live tracking is not delayed.
138
+ */
139
+ transitionMs?: number;
107
140
  }
108
141
 
109
142
  /**
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { useRef as B, useCallback as _, useLayoutEffect as J, useEffect as K, forwardRef as U } from "react";
2
- import { jsx as G } from "react/jsx-runtime";
3
- const D = {
1
+ import { useRef as k, useCallback as j, useLayoutEffect as tt, useEffect as _, forwardRef as z } from "react";
2
+ import { jsx as Q } from "react/jsx-runtime";
3
+ const G = {
4
4
  i: 3,
5
5
  l: 3,
6
6
  1: 3,
@@ -22,14 +22,14 @@ const D = {
22
22
  c: 1,
23
23
  e: 1
24
24
  // similar bowls
25
- }, V = {
25
+ }, I = {
26
26
  /** Applied to each word span in field mode */
27
27
  word: "mt-word",
28
28
  /** Applied to each character span in legibility mode */
29
29
  char: "mt-char",
30
30
  /** Applied to measurement probe spans (never in final output) */
31
31
  probe: "mt-probe"
32
- }, C = {
32
+ }, Y = {
33
33
  axes: { wght: [300, 500] },
34
34
  radius: 120,
35
35
  falloff: "quadratic",
@@ -37,196 +37,321 @@ const D = {
37
37
  wdthBoost: 6,
38
38
  scope: "document"
39
39
  };
40
- function k(t, e = []) {
41
- return t.nodeType === Node.TEXT_NODE ? e.push(t) : t.childNodes.forEach((n) => k(n, e)), e;
40
+ function J(t, o = []) {
41
+ return t.nodeType === Node.TEXT_NODE ? o.push(t) : t.childNodes.forEach((r) => J(r, o)), o;
42
42
  }
43
- function $(t, e, n) {
44
- if (!t || t === "normal") return `"${e}" ${n}`;
45
- const c = new RegExp(`(["'])${e}\\1\\s+[\\d.eE+-]+`), o = `"${e}" ${n}`;
46
- return c.test(t) ? t.replace(c, o) : `${t}, ${o}`;
43
+ function D(t, o, r) {
44
+ if (!t || t === "normal") return `"${o}" ${r}`;
45
+ const l = new RegExp(`(["'])${o}\\1\\s+[\\d.eE+-]+`), c = `"${o}" ${r}`;
46
+ return l.test(t) ? t.replace(l, c) : `${t}, ${c}`;
47
47
  }
48
- function I(t, e) {
49
- let n = t;
50
- for (const [c, o] of Object.entries(e))
51
- n = $(n, c, o);
52
- return n;
48
+ function R(t, o) {
49
+ let r = t;
50
+ for (const [l, c] of Object.entries(o))
51
+ r = D(r, l, c);
52
+ return r;
53
53
  }
54
- function P(t, e, n) {
55
- if (e.opacity !== void 0) {
56
- const [c, o] = e.opacity;
57
- t.style.opacity = String(c + (o - c) * n);
54
+ function Z(t, o, r) {
55
+ if (o.opacity !== void 0) {
56
+ const [l, c] = o.opacity;
57
+ t.style.opacity = String(l + (c - l) * r);
58
58
  }
59
- e.italic === !0 && (t.style.fontStyle = n > 0.5 ? "italic" : "");
59
+ o.italic === !0 && (t.style.fontStyle = r > 0.5 ? "italic" : "");
60
60
  }
61
- function j(t, e) {
62
- e.opacity !== void 0 && (t.style.opacity = String(e.opacity[0])), e.italic === !0 && (t.style.fontStyle = "");
61
+ function P(t, o) {
62
+ o.opacity !== void 0 && (t.style.opacity = String(o.opacity[0])), o.italic === !0 && (t.style.fontStyle = "");
63
63
  }
64
- function z(t) {
65
- const e = t.cloneNode(!0), n = e.querySelectorAll(
66
- `.${V.word}, .${V.char}`
64
+ function et(t) {
65
+ const o = t.cloneNode(!0), r = o.querySelectorAll(
66
+ `.${I.word}, .${I.char}`
67
67
  );
68
- return Array.from(n).reverse().forEach((o) => {
69
- const y = o.parentNode;
70
- if (y) {
71
- for (; o.firstChild; ) y.insertBefore(o.firstChild, o);
72
- y.removeChild(o);
68
+ return Array.from(r).reverse().forEach((c) => {
69
+ const u = c.parentNode;
70
+ if (u) {
71
+ for (; c.firstChild; ) u.insertBefore(c.firstChild, c);
72
+ u.removeChild(c);
73
73
  }
74
- }), e.innerHTML;
74
+ }), o.innerHTML;
75
75
  }
76
- function rt(t, e) {
77
- t.innerHTML = e;
76
+ function ut(t, o) {
77
+ t.innerHTML = o;
78
78
  }
79
- function Q(t, e, n = {}) {
79
+ function nt(t, o, r = {}) {
80
80
  if (typeof window > "u") return () => {
81
81
  };
82
82
  if (window.matchMedia("(prefers-reduced-motion: reduce)").matches)
83
- return t.innerHTML = e, () => {
83
+ return t.innerHTML = o, () => {
84
84
  };
85
- const c = n.wdthBoost ?? C.wdthBoost, o = n.radius ?? C.radius, y = n.falloff ?? C.falloff, m = n.scope ?? C.scope, p = n.props, d = window.scrollY;
86
- t.innerHTML = e;
87
- const l = getComputedStyle(t).fontVariationSettings, A = l.match(/"wdth"\s+([\d.eE+-]+)/), h = A ? parseFloat(A[1]) : 100, q = k(t), w = [];
88
- for (const r of q) {
89
- const s = r.textContent ?? "";
90
- if (!s || !s.split("").some((i) => i in D)) continue;
91
- const u = document.createDocumentFragment();
92
- for (const i of s) {
93
- const L = D[i];
94
- if (L === void 0) {
95
- const a = u.lastChild;
96
- a && a.nodeType === Node.TEXT_NODE ? a.textContent += i : u.appendChild(document.createTextNode(i));
85
+ const l = r.wdthBoost ?? Y.wdthBoost, c = r.radius ?? Y.radius, u = r.falloff ?? Y.falloff, w = r.scope ?? Y.scope, v = r.props, m = r.transitionMs ?? 0, x = window.scrollY;
86
+ t.innerHTML = o;
87
+ const L = getComputedStyle(t).fontVariationSettings, N = L.match(/"wdth"\s+([\d.eE+-]+)/), y = N ? parseFloat(N[1]) : 100, A = J(t), M = [];
88
+ for (const e of A) {
89
+ const n = e.textContent ?? "";
90
+ if (!n || !n.split("").some((a) => a in G)) continue;
91
+ const h = document.createDocumentFragment();
92
+ for (const a of n) {
93
+ const $ = G[a];
94
+ if ($ === void 0) {
95
+ const E = h.lastChild;
96
+ E && E.nodeType === Node.TEXT_NODE ? E.textContent += a : h.appendChild(document.createTextNode(a));
97
97
  } else {
98
- const a = document.createElement("span");
99
- a.className = V.char, a.style.fontVariationSettings = $(l, "wdth", h), a.textContent = i, u.appendChild(a), w.push({ span: a, riskLevel: L });
98
+ const E = document.createElement("span");
99
+ E.className = I.char, E.style.fontVariationSettings = D(L, "wdth", y), E.textContent = a, h.appendChild(E), M.push({ span: E, riskLevel: $ });
100
100
  }
101
101
  }
102
- r.parentNode.replaceChild(u, r);
102
+ e.parentNode.replaceChild(h, e);
103
103
  }
104
104
  if (requestAnimationFrame(() => {
105
- typeof window < "u" && Math.abs(window.scrollY - d) > 2 && window.scrollTo({ top: d, behavior: "instant" });
106
- }), w.length === 0) return () => {
105
+ typeof window < "u" && Math.abs(window.scrollY - x) > 2 && window.scrollTo({ top: x, behavior: "instant" });
106
+ }), M.length === 0) return () => {
107
107
  };
108
- p && w.forEach(({ span: r }) => j(r, p));
109
- let g = -9999, v = -9999, M = !1, f = 0, F = !0;
110
- function N() {
111
- if (!F) return;
112
- if (!M) {
113
- w.forEach(({ span: s }) => {
114
- s.style.fontVariationSettings = $(l, "wdth", h), p && j(s, p);
115
- }), f = 0;
108
+ v && M.forEach(({ span: e }) => P(e, v));
109
+ let p = -9999, q = -9999, g = !1, i = 0, V = !0, s = null;
110
+ function d() {
111
+ if (!V) return;
112
+ if (!g) {
113
+ M.forEach(({ span: n }) => {
114
+ m > 0 && (n.style.transition = `font-variation-settings ${m}ms ease`), n.style.fontVariationSettings = D(L, "wdth", y), v && P(n, v);
115
+ }), m > 0 && (s !== null && clearTimeout(s), s = setTimeout(() => {
116
+ M.forEach(({ span: n }) => {
117
+ n.style.transition = "";
118
+ }), s = null;
119
+ }, m)), i = 0;
116
120
  return;
117
121
  }
118
- const r = w.map(({ span: s }) => s.getBoundingClientRect());
119
- w.forEach(({ span: s, riskLevel: E }, u) => {
120
- const i = r[u], L = i.left + i.width / 2, a = i.top + i.height / 2, b = Math.sqrt((g - L) ** 2 + (v - a) ** 2), T = Math.max(0, 1 - b / o), S = y === "quadratic" ? T * T : T, R = c * (E / 3) * S;
121
- s.style.fontVariationSettings = $(l, "wdth", h + R), p && P(s, p, S);
122
- }), f = requestAnimationFrame(N);
122
+ const e = M.map(({ span: n }) => n.getBoundingClientRect());
123
+ M.forEach(({ span: n, riskLevel: T }, h) => {
124
+ n.style.transition = "";
125
+ const a = e[h], $ = a.left + a.width / 2, E = a.top + a.height / 2, X = Math.sqrt((p - $) ** 2 + (q - E) ** 2), B = Math.max(0, 1 - X / c), O = u === "quadratic" ? B * B : B, H = l * (T / 3) * O;
126
+ n.style.fontVariationSettings = D(L, "wdth", y + H), v && Z(n, v, O);
127
+ }), i = requestAnimationFrame(d);
123
128
  }
124
- function O(r) {
125
- g = r.clientX, v = r.clientY, M || (M = !0), f === 0 && (f = requestAnimationFrame(N));
129
+ function C(e) {
130
+ p = e.clientX, q = e.clientY, g || (g = !0), i === 0 && (i = requestAnimationFrame(d));
126
131
  }
127
- function Y() {
128
- M = !1, f === 0 && (f = requestAnimationFrame(N));
132
+ function S() {
133
+ g = !1, i === 0 && (i = requestAnimationFrame(d));
129
134
  }
130
- const x = m === "document" ? document : t;
131
- return x.addEventListener("mousemove", O), x.addEventListener("mouseleave", Y), () => {
132
- F = !1, cancelAnimationFrame(f), x.removeEventListener("mousemove", O), x.removeEventListener("mouseleave", Y), t.innerHTML = e;
135
+ function F(e) {
136
+ e.touches.length !== 0 && (p = e.touches[0].clientX, q = e.touches[0].clientY, g || (g = !0), i === 0 && (i = requestAnimationFrame(d)));
137
+ }
138
+ function b() {
139
+ g = !1, i === 0 && (i = requestAnimationFrame(d));
140
+ }
141
+ const f = w === "document" ? document : t;
142
+ return f.addEventListener("mousemove", C), f.addEventListener("mouseleave", S), f.addEventListener("touchmove", F, { passive: !0 }), f.addEventListener("touchend", b), () => {
143
+ V = !1, cancelAnimationFrame(i), s !== null && clearTimeout(s), f.removeEventListener("mousemove", C), f.removeEventListener("mouseleave", S), f.removeEventListener("touchmove", F), f.removeEventListener("touchend", b), t.innerHTML = o;
133
144
  };
134
145
  }
135
- function Z(t, e, n = {}) {
146
+ function ot(t, o, r = {}) {
136
147
  if (typeof window > "u") return () => {
137
148
  };
138
149
  if (window.matchMedia("(prefers-reduced-motion: reduce)").matches)
139
- return t.innerHTML = e, () => {
150
+ return t.innerHTML = o, () => {
140
151
  };
141
- const c = n.axes ?? C.axes, o = n.radius ?? C.radius, y = n.falloff ?? C.falloff, m = n.magnetMode ?? C.magnetMode, p = n.scope ?? C.scope, d = n.props, l = window.scrollY;
142
- t.innerHTML = e;
143
- const A = k(t), h = [];
144
- for (const r of A) {
145
- const s = r.textContent ?? "";
146
- if (!s.trim()) continue;
147
- const E = s.split(/(\S+)/), u = document.createDocumentFragment();
148
- for (let i = 0; i < E.length; i += 2) {
149
- const L = E[i], a = E[i + 1];
150
- if (!a) continue;
151
- const T = E[i + 3] === void 0 ? E[i + 2] ?? "" : "", S = document.createElement("span");
152
- S.className = V.word, S.textContent = L + a + T, u.appendChild(S), h.push(S);
152
+ const l = r.axes ?? Y.axes, c = r.radius ?? Y.radius, u = r.falloff ?? Y.falloff, w = r.magnetMode ?? Y.magnetMode, v = r.scope ?? Y.scope, m = r.props, x = r.transitionMs ?? 0, L = window.scrollY;
153
+ t.innerHTML = o;
154
+ const N = J(t), y = [];
155
+ for (const e of N) {
156
+ const n = e.textContent ?? "";
157
+ if (!n.trim()) continue;
158
+ const T = n.split(/(\S+)/), h = document.createDocumentFragment();
159
+ for (let a = 0; a < T.length; a += 2) {
160
+ const $ = T[a], E = T[a + 1];
161
+ if (!E) continue;
162
+ const B = T[a + 3] === void 0 ? T[a + 2] ?? "" : "", O = document.createElement("span");
163
+ O.className = I.word, O.textContent = $ + E + B, h.appendChild(O), y.push(O);
153
164
  }
154
- r.parentNode.replaceChild(u, r);
165
+ e.parentNode.replaceChild(h, e);
155
166
  }
156
167
  if (requestAnimationFrame(() => {
157
- typeof window < "u" && Math.abs(window.scrollY - l) > 2 && window.scrollTo({ top: l, behavior: "instant" });
158
- }), h.length === 0) return () => {
168
+ typeof window < "u" && Math.abs(window.scrollY - L) > 2 && window.scrollTo({ top: L, behavior: "instant" });
169
+ }), y.length === 0) return () => {
159
170
  };
160
- const q = getComputedStyle(t).fontVariationSettings, w = I(
161
- q,
162
- Object.fromEntries(Object.entries(c).map(([r, [s]]) => [r, s]))
171
+ const A = getComputedStyle(t).fontVariationSettings, M = R(
172
+ A,
173
+ Object.fromEntries(Object.entries(l).map(([e, [n]]) => [e, n]))
163
174
  );
164
- h.forEach((r) => {
165
- r.style.fontVariationSettings = w, d && j(r, d);
175
+ y.forEach((e) => {
176
+ e.style.fontVariationSettings = M, m && P(e, m);
166
177
  });
167
- let g = -9999, v = -9999, M = !1, f = 0, F = !0;
168
- function N() {
169
- if (!F) return;
170
- if (!M) {
171
- h.forEach((s) => {
172
- s.style.fontVariationSettings = w, d && j(s, d);
173
- }), f = 0;
178
+ let p = -9999, q = -9999, g = !1, i = 0, V = !0, s = null;
179
+ function d() {
180
+ if (!V) return;
181
+ if (!g) {
182
+ y.forEach((n) => {
183
+ x > 0 && (n.style.transition = `font-variation-settings ${x}ms ease`), n.style.fontVariationSettings = M, m && P(n, m);
184
+ }), x > 0 && (s !== null && clearTimeout(s), s = setTimeout(() => {
185
+ y.forEach((n) => {
186
+ n.style.transition = "";
187
+ }), s = null;
188
+ }, x)), i = 0;
174
189
  return;
175
190
  }
176
- const r = h.map((s) => s.getBoundingClientRect());
177
- h.forEach((s, E) => {
178
- const u = r[E], i = u.left + u.width / 2, L = u.top + u.height / 2, a = Math.sqrt((g - i) ** 2 + (v - L) ** 2), b = Math.max(0, 1 - a / o), T = y === "quadratic" ? b * b : b, S = m === "repel" ? 1 - T : T, R = {};
179
- for (const H of Object.keys(c)) {
180
- const [X, W] = c[H] ?? [300, 500];
181
- R[H] = X + (W - X) * S;
191
+ const e = y.map((n) => n.getBoundingClientRect());
192
+ y.forEach((n, T) => {
193
+ n.style.transition = "";
194
+ const h = e[T], a = h.left + h.width / 2, $ = h.top + h.height / 2, E = Math.sqrt((p - a) ** 2 + (q - $) ** 2), X = Math.max(0, 1 - E / c), B = u === "quadratic" ? X * X : X, O = w === "repel" ? 1 - B : B, H = {};
195
+ for (const K of Object.keys(l)) {
196
+ const [U, W] = l[K] ?? [300, 500];
197
+ H[K] = U + (W - U) * O;
182
198
  }
183
- s.style.fontVariationSettings = I(q, R), d && P(s, d, T);
184
- }), f = requestAnimationFrame(N);
199
+ n.style.fontVariationSettings = R(A, H), m && Z(n, m, B);
200
+ }), i = requestAnimationFrame(d);
201
+ }
202
+ function C(e) {
203
+ p = e.clientX, q = e.clientY, g || (g = !0), i === 0 && (i = requestAnimationFrame(d));
185
204
  }
186
- function O(r) {
187
- g = r.clientX, v = r.clientY, M || (M = !0), f === 0 && (f = requestAnimationFrame(N));
205
+ function S() {
206
+ g = !1, i === 0 && (i = requestAnimationFrame(d));
188
207
  }
189
- function Y() {
190
- M = !1, f === 0 && (f = requestAnimationFrame(N));
208
+ function F(e) {
209
+ e.touches.length !== 0 && (p = e.touches[0].clientX, q = e.touches[0].clientY, g || (g = !0), i === 0 && (i = requestAnimationFrame(d)));
191
210
  }
192
- const x = p === "document" ? document : t;
193
- return x.addEventListener("mousemove", O), x.addEventListener("mouseleave", Y), () => {
194
- F = !1, cancelAnimationFrame(f), x.removeEventListener("mousemove", O), x.removeEventListener("mouseleave", Y), t.innerHTML = e;
211
+ function b() {
212
+ g = !1, i === 0 && (i = requestAnimationFrame(d));
213
+ }
214
+ const f = v === "document" ? document : t;
215
+ return f.addEventListener("mousemove", C), f.addEventListener("mouseleave", S), f.addEventListener("touchmove", F, { passive: !0 }), f.addEventListener("touchend", b), () => {
216
+ V = !1, cancelAnimationFrame(i), s !== null && clearTimeout(s), f.removeEventListener("mousemove", C), f.removeEventListener("mouseleave", S), f.removeEventListener("touchmove", F), f.removeEventListener("touchend", b), t.innerHTML = o;
195
217
  };
196
218
  }
197
- function tt(t) {
198
- const e = B(null), n = B(null), c = B(t);
199
- c.current = t;
200
- const o = B(null), y = t.mode ?? "field", { axes: m, radius: p, falloff: d, magnetMode: l, wdthBoost: A, scope: h } = t, q = m ? JSON.stringify(m) : void 0, w = t.props ? JSON.stringify(t.props) : void 0, g = _(() => {
201
- const v = e.current;
202
- if (!v) return;
203
- n.current === null && (n.current = z(v)), o.current && (o.current(), o.current = null), (c.current.mode ?? "field") === "field" ? o.current = Z(v, n.current, c.current) : o.current = Q(v, n.current, c.current);
204
- }, [y, q, p, d, l, A, h, w]);
205
- return J(() => (g(), () => {
206
- o.current && (o.current(), o.current = null);
207
- }), [g]), K(() => {
208
- document.fonts.ready.then(g);
209
- }, [g]), e;
219
+ function rt(t) {
220
+ const o = k(null), r = k(null), l = k(t);
221
+ l.current = t;
222
+ const c = k(null), u = t.mode ?? "field", { axes: w, radius: v, falloff: m, magnetMode: x, wdthBoost: L, scope: N } = t, y = w ? JSON.stringify(w) : void 0, A = t.props ? JSON.stringify(t.props) : void 0, M = j(() => {
223
+ const p = o.current;
224
+ if (!p) return;
225
+ r.current === null && (r.current = et(p)), c.current && (c.current(), c.current = null), (l.current.mode ?? "field") === "field" ? c.current = ot(p, r.current, l.current) : c.current = nt(p, r.current, l.current);
226
+ }, [u, y, v, m, x, L, N, A]);
227
+ return tt(() => (M(), () => {
228
+ c.current && (c.current(), c.current = null);
229
+ }), [M]), _(() => {
230
+ document.fonts.ready.then(M);
231
+ }, [M]), o;
210
232
  }
211
- const et = U(
212
- function({ children: e, as: n = "p", className: c, style: o, ...y }, m) {
213
- const p = tt(y), d = _(
214
- (l) => {
215
- p.current = l, typeof m == "function" ? m(l) : m && (m.current = l);
233
+ const st = z(
234
+ function({ children: o, as: r = "p", className: l, style: c, ...u }, w) {
235
+ const v = rt(u), m = j(
236
+ (x) => {
237
+ v.current = x, typeof w == "function" ? w(x) : w && (w.current = x);
238
+ },
239
+ // eslint-disable-next-line react-hooks/exhaustive-deps
240
+ [w]
241
+ );
242
+ return /* @__PURE__ */ Q(r, { ref: m, className: l, style: c, children: o });
243
+ }
244
+ );
245
+ st.displayName = "MagnetTypeText";
246
+ const ct = z(
247
+ function({
248
+ children: o,
249
+ as: r = "p",
250
+ className: l,
251
+ style: c,
252
+ minWeight: u = 300,
253
+ maxWeight: w = 600,
254
+ proximityRadius: v,
255
+ spreadRadius: m,
256
+ fixedAxes: x = {}
257
+ }, L) {
258
+ const N = k(null), y = k(null), A = k([]), M = j(
259
+ (s) => {
260
+ N.current = s, typeof L == "function" ? L(s) : L && (L.current = s);
216
261
  },
217
262
  // eslint-disable-next-line react-hooks/exhaustive-deps
218
- [m]
263
+ [L]
264
+ );
265
+ function p(s) {
266
+ const d = [`'wght' ${s.toFixed(0)}`];
267
+ for (const [C, S] of Object.entries(x)) d.push(`'${C}' ${S}`);
268
+ return d.join(", ");
269
+ }
270
+ _(() => {
271
+ var b;
272
+ if (!m) return;
273
+ const s = N.current;
274
+ if (!s) return;
275
+ const d = [], C = document.createTreeWalker(s, NodeFilter.SHOW_TEXT), S = [];
276
+ let F;
277
+ for (; F = C.nextNode(); ) S.push(F);
278
+ for (const f of S) {
279
+ const e = f.textContent ?? "";
280
+ if (!/\S/.test(e)) continue;
281
+ const n = e.split(/(\s+)/), T = document.createDocumentFragment();
282
+ for (const h of n)
283
+ if (h !== "")
284
+ if (/^\s+$/.test(h))
285
+ T.appendChild(document.createTextNode(h));
286
+ else {
287
+ const a = document.createElement("span");
288
+ a.style.fontVariationSettings = p(u), a.textContent = h, d.push(a), T.appendChild(a);
289
+ }
290
+ (b = f.parentNode) == null || b.replaceChild(T, f);
291
+ }
292
+ A.current = d;
293
+ }, []);
294
+ function q(s, d) {
295
+ const C = N.current;
296
+ if (!C) return;
297
+ const S = C.getBoundingClientRect(), F = Math.max(S.left - s, 0, s - S.right), b = Math.max(S.top - d, 0, d - S.bottom), f = Math.sqrt(F * F + b * b);
298
+ if (v !== void 0 && !m) {
299
+ const n = 1 - (1 - Math.max(0, 1 - f / v)) ** 2;
300
+ C.style.fontVariationSettings = p(u + (w - u) * n);
301
+ return;
302
+ }
303
+ if (m) {
304
+ if (v !== void 0 && f > v) {
305
+ C.style.fontVariationSettings = p(u);
306
+ for (const e of A.current) e.style.fontVariationSettings = p(u);
307
+ return;
308
+ }
309
+ for (const e of A.current) {
310
+ const n = e.getBoundingClientRect(), T = (n.left + n.right) / 2, h = (n.top + n.bottom) / 2, a = Math.sqrt((s - T) ** 2 + (d - h) ** 2), E = 1 - (1 - Math.max(0, 1 - a / m)) ** 2;
311
+ e.style.fontVariationSettings = p(u + (w - u) * E);
312
+ }
313
+ }
314
+ }
315
+ const g = j(
316
+ (s) => {
317
+ y.current = { x: s.clientX, y: s.clientY }, q(s.clientX, s.clientY);
318
+ },
319
+ [u, w, v, m]
320
+ ), i = j(
321
+ () => {
322
+ y.current && q(y.current.x, y.current.y);
323
+ },
324
+ [u, w, v, m]
325
+ ), V = j(
326
+ () => {
327
+ y.current = null;
328
+ const s = N.current;
329
+ s && (s.style.fontVariationSettings = p(u));
330
+ for (const d of A.current) d.style.fontVariationSettings = p(u);
331
+ },
332
+ [u]
333
+ );
334
+ return _(() => (window.addEventListener("mousemove", g, { passive: !0 }), window.addEventListener("scroll", i, { passive: !0, capture: !0 }), document.documentElement.addEventListener("mouseleave", V), () => {
335
+ window.removeEventListener("mousemove", g), window.removeEventListener("scroll", i, { capture: !0 }), document.documentElement.removeEventListener("mouseleave", V);
336
+ }), [g, i, V]), /* @__PURE__ */ Q(
337
+ r,
338
+ {
339
+ ref: M,
340
+ className: l,
341
+ style: { fontVariationSettings: p(u), ...c },
342
+ children: o
343
+ }
219
344
  );
220
- return /* @__PURE__ */ G(n, { ref: d, className: c, style: o, children: e });
221
345
  }
222
346
  );
223
- et.displayName = "MagnetTypeText";
347
+ ct.displayName = "MagnetBlock";
224
348
  export {
225
- V as MAGNET_TYPE_CLASSES,
226
- et as MagnetTypeText,
227
- Q as applyMagnetType,
228
- z as getCleanHTML,
229
- rt as removeMagnetType,
230
- Z as startMagnetType,
231
- tt as useMagnetType
349
+ I as MAGNET_TYPE_CLASSES,
350
+ ct as MagnetBlock,
351
+ st as MagnetTypeText,
352
+ nt as applyMagnetType,
353
+ et as getCleanHTML,
354
+ ut as removeMagnetType,
355
+ ot as startMagnetType,
356
+ rt as useMagnetType
232
357
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liiift-studio/magnettype",
3
- "version": "1.0.8",
3
+ "version": "1.1.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",
@@ -38,8 +38,8 @@
38
38
  "devDependencies": {
39
39
  "@types/react": "^19.0.0",
40
40
  "@vitejs/plugin-react": "^4.0.0",
41
- "happy-dom": "^12.0.0",
42
- "next": "16.2.2",
41
+ "happy-dom": "^20.9.0",
42
+ "next": "^16.2.6",
43
43
  "react": "^19.0.0",
44
44
  "typescript": "^5.0.0",
45
45
  "vite": "^6.0.0",
@@ -83,5 +83,8 @@
83
83
  "sideEffects": false,
84
84
  "publishConfig": {
85
85
  "access": "public"
86
+ },
87
+ "overrides": {
88
+ "postcss": ">=8.5.10"
86
89
  }
87
90
  }