@re-dev/react-truncate
Version:
Provides `Truncate`, `MiddleTruncate` and `ShowMore` React components for truncating multi-line spans and adding an ellipsis.
15 lines (14 loc) • 12.5 kB
JavaScript
/**
* name: @re-dev/react-truncate
* version: v0.6.0
* description: Provides `Truncate`, `MiddleTruncate` and `ShowMore` React components for truncating multi-line spans and adding an ellipsis.
* author: chengpeiquan <chengpeiquan@chengpeiquan.com>
* homepage: https://truncate.js.org
* license: MIT
*/
"use client";import A,{useCallback as J,useEffect as W,useMemo as q,useRef as G,useState as z}from"react";import w from"react";var me=n=>n==="class"?"className":n,de=n=>n.split(";").map(t=>t.trim()).filter(Boolean).reduce((t,e)=>{let[r,...o]=e.split(":"),i=r?.trim(),s=o.join(":").trim();if(!i||!s)return t;let u=i.replace(/-([a-z])/g,(M,a)=>a.toUpperCase());return t[u]=s,t},{}),fe=n=>Object.entries(n).reduce((t,[e,r])=>{let o=me(e);return t[o]=o==="style"?de(r):r,t},{}),D=(n,t="markup")=>n.reduce((e,r,o)=>{let i=`${t}-${o}`;if(r.type==="text")return r.text&&e.push(r.text),e;if(r.type==="line-break")return e.push(w.createElement("br",{key:i})),e;let s=D(r.children,`${i}-child`);return s.length===0||e.push(w.createElement(r.tagName,{key:i,...fe(r.attributes)},...s)),e},[]),ge=(n,t)=>{if(t<=0)return{taken:[],rest:[n],remaining:t};if(n.type==="text")return n.text.length<=t?{taken:[n],rest:[],remaining:t-n.text.length}:{taken:[{...n,text:n.text.slice(0,t)}],rest:[{...n,text:n.text.slice(t)}],remaining:0};if(n.type==="line-break")return t>=1?{taken:[n],rest:[],remaining:t-1}:{taken:[],rest:[n],remaining:t};let e=O(n.children,t);return{taken:e.taken.length?[{...n,children:e.taken}]:[],rest:e.rest.length?[{...n,children:e.rest}]:[],remaining:e.remaining}},O=(n,t)=>{let e=[],r=[],o=t;return n.forEach(i=>{if(o<=0){r.push(i);return}let s=ge(i,o);e.push(...s.taken),r.push(...s.rest),o=s.remaining}),{taken:e,rest:r,remaining:o}},F=n=>{if(n.length===0)return n;let[t,...e]=n;if(t.type==="text"){let r=t.text.replace(/^\s+/,"");return r?[{...t,text:r},...e]:F(e)}if(t.type==="element"){let r=F(t.children);return r.length===0?F(e):[{...t,children:r},...e]}return n},v=n=>{if(n.length===0)return n;let t=n.slice(0,-1),e=n.at(-1);if(!e)return n;if(e.type==="line-break")return v(t);if(e.type==="text"){let o=e.text.replace(/\s+$/,"");return o?[...t,{...e,text:o}]:v(t)}let r=v(e.children);return r.length===0?v(t):[...t,{...e,children:r}]},X=n=>n.reduce((t,e)=>e.type==="text"?t+e.text.length:e.type==="line-break"?t+1:t+X(e.children),0),Q=(n,t,e)=>{let r=n;return t.map((o,i)=>{let s=O(r,o.length);r=F(s.rest);let u=D(s.taken,`markup-line-${i}`);return i===t.length-1?w.createElement("span",{key:`markup-line-${i}`},u,e):u.length===0?w.createElement("br",{key:`markup-line-${i}`}):w.createElement(w.Fragment,{key:`markup-line-${i}`},u,w.createElement("br",null))})},Y=(n,t,e,r=!1)=>{let o=O(n,t),i=r?v(o.taken):o.taken;return[...D(i,"markup-prefix"),w.createElement(w.Fragment,{key:"markup-ellipsis"},e)]};var he=(n,t)=>n.replace(/\r\n|\r|\n/g,t),xe=n=>Array.from(n.attributes).reduce((t,e)=>(t[e.name]=e.value,t),{}),j=(n,t)=>n?Array.from(n.childNodes).flatMap(e=>{if(e instanceof HTMLBRElement)return[{type:"line-break"}];if(e.nodeType===Node.TEXT_NODE){let r=he(e.textContent||"",t);return r?[{type:"text",text:r}]:[]}if(e.nodeType===Node.ELEMENT_NODE){let r=e;return[{type:"element",tagName:r.tagName.toLowerCase(),attributes:xe(r),children:j(r,t)}]}return[]}):[];var Te=(n,t)=>n.replace(/\r\n|\r|\n/g,t),P=n=>{let t=Number.parseFloat(n);return Number.isFinite(t)?t:0},ye=(n,t)=>{let e=n?.parentElement;if(!e)return t;let r=e.getBoundingClientRect().width,o=window.getComputedStyle(e),i=P(o.paddingLeft)+P(o.paddingRight),s=P(o.borderLeftWidth)+P(o.borderRightWidth),u=Math.floor(r-i-s);return u>0?u:t},be=(n,t)=>{let e=document.createElement("span"),r=n?window.getComputedStyle(n):null;return e.setAttribute("aria-hidden","true"),e.style.position="fixed",e.style.top="0",e.style.left="0",e.style.visibility="hidden",e.style.pointerEvents="none",e.style.display="block",e.style.margin="0",e.style.padding="0",e.style.border="0",e.style.boxSizing="content-box",e.style.width=`${Math.max(t,1)}px`,e.style.whiteSpace=r?.whiteSpace||"normal",e.style.wordBreak=r?.wordBreak||"normal",e.style.overflowWrap=r?.overflowWrap||"break-word",e.style.font=r?.font||"",e.style.fontFamily=r?.fontFamily||"",e.style.fontSize=r?.fontSize||"",e.style.fontStyle=r?.fontStyle||"",e.style.fontWeight=r?.fontWeight||"",e.style.letterSpacing=r?.letterSpacing||"",e.style.lineHeight=r?.lineHeight||"",e.style.wordSpacing=r?.wordSpacing||"",e.style.textTransform=r?.textTransform||"",e.style.textIndent=r?.textIndent||"",e.style.direction=r?.direction||"",e},Me=(n,t)=>{t&&Array.from(t.childNodes).forEach(e=>{n.appendChild(e.cloneNode(!0))})},ee=n=>{let t=Array.from(n.childNodes);for(let e=t.length-1;e>=0;e-=1){let r=t[e];if(r instanceof HTMLBRElement){r.remove();continue}if(r.nodeType===Node.TEXT_NODE){let o=(r.textContent||"").replace(/\s+$/,"");if(!o){r.remove();continue}r.textContent=o;return}if(r.nodeType===Node.ELEMENT_NODE){if(ee(r),r.childNodes.length===0){r.remove();continue}return}r.remove()}},te=(n,t,e)=>{let r=Array.from(n.childNodes),o=t;return r.forEach(i=>{if(o<=0){i.remove();return}if(i instanceof HTMLBRElement){o-=1;return}if(i.nodeType===Node.TEXT_NODE){let s=Te(i.textContent||"",e);if(s.length<=o){i.textContent=s,o-=s.length;return}i.textContent=s.slice(0,o),o=0;return}if(i.nodeType===Node.ELEMENT_NODE){o=te(i,o,e),i.childNodes.length===0&&i.remove();return}i.remove()}),o},Z=({ellipsisNode:n,node:t,separator:e,trimWhitespace:r,visibleLength:o})=>{let i=document.createDocumentFragment(),s=t.cloneNode(!0);return typeof o=="number"&&(te(s,o,e),r&&ee(s)),Array.from(s.childNodes).forEach(u=>{i.appendChild(u)}),typeof o=="number"&&Me(i,n),i},V=(n,t)=>(n.replaceChildren(t),n.getBoundingClientRect().height),ne=({ellipsis:n,ellipsisNode:t,fallbackDidTruncate:e,fallbackVisibleTextLines:r,lines:o,node:i,rootNode:s,separator:u,trimWhitespace:M})=>{if(!i)return{didTruncate:!1,result:null};let a=j(i,u),S=X(a),l={didTruncate:e,result:e?Q(a,r,n):null};if(S===0)return{didTruncate:!1,result:null};let b=be(s,ye(s,s?.parentElement?.getBoundingClientRect().width||0));document.body.appendChild(b);try{let h=document.createDocumentFragment();h.appendChild(document.createTextNode("A"));let f=V(b,h);if(f===0)return l;let c=Math.max(f,1)*o+.5,g=V(b,Z({ellipsisNode:t,node:i,separator:u,trimWhitespace:M}));if(g===0)return l;if(g<=c)return{didTruncate:!1,result:null};let x=0,d=S,p=0;for(;x<=d;){let T=Math.floor((x+d)/2);V(b,Z({ellipsisNode:t,node:i,separator:u,trimWhitespace:M,visibleLength:T}))<=c?(p=T,x=T+1):d=T-1}return{didTruncate:!0,result:Y(a,p,n,M)}}finally{b.remove()}};import le from"react";import _ from"react";var re=n=>n?.offsetWidth,I=n=>n.replace(/\s+$/,""),oe=(n,t,e)=>{if(t===e.length-1)return _.createElement("span",{key:t},n);{let r=_.createElement("br",{key:`${t}br`});return n?[_.createElement("span",{key:t},n),r]:r}},ie=(n,t)=>{let e=document.createElement("div"),r="innerText"in window.HTMLElement.prototype?"innerText":"textContent";e.innerHTML=n?.innerHTML.replace(/\r\n|\r|\n/g,t)||"";let o=e[r],i=document.createElement("div");return i.innerHTML="foo<br/>bar",i[r]?.replace(/\r\n|\r/g,`
`)!==`foo
bar`&&(e.innerHTML=e.innerHTML.replace(/<br.*?[/]?>/gi,`
`),o=e[r]),o||""},se=({end:n,lastLineText:t,fullText:e,targetWidth:r,ellipsisWidth:o,measureWidth:i})=>{let s=t.length,u=Math.abs(n),M=u>s?0:s-u,a=t.slice(0,M),S=M===0?-s:n,l=e.slice(S),b=(f,m)=>Math.floor(i(f)+i(m)+o),h=b(a,l);if(h<r){for(;a.length<s&&a.length+l.length<e.length&&h<r;){let f=t[a.length],m=a+f,c=b(m,l);if(c<=r)a=m,h=c;else break}for(;l.length<e.length&&a.length+l.length<e.length&&h<r;){let m=e[e.length-l.length-1]+l,c=b(a,m);if(c<=r)l=m,h=c;else break}}for(;h>r&&(a.length>0||l.length>0);){let f=i(a),m=i(l),c=f+m,g=f/c,x=m/c;a.length>0&&l.length>0?g>=x?a=a.slice(0,a.length-1):l=l.slice(1):a.length>0?a=a.slice(0,a.length-1):l.length>0&&(l=l.slice(1)),h=b(a,l)}if(h<r){let f=r-h,m=a.length+l.length<e.length,c=m&&M>0?t[a.length]:null,g=m?e[e.length-l.length-1]:null;c&&i(c)<=f?a+=c:g&&i(g)<=f&&(l=g+l)}return{startFragment:a,endFragment:l}};var ae=({ellipsis:n,ellipsisRef:t,end:e,lines:r,measureWidth:o,middleTruncate:i,separator:s,targetWidth:u,textRef:M,trimWhitespace:a})=>{let S=[],l=[],b=ie(M,s),h=b.split(`
`).map(c=>c.split(s)),f=re(t)||0,m=!0;for(let c=1;c<=r;c++){let g=h[0];if(g.length===0){S.push(),l.push(""),h.shift(),c--;continue}let x=g.join(s)||"",d=typeof x=="string"?x:"";if(o(d)<=u&&h.length===1){m=!1,S.push(x),l.push(d);break}if(c===r){let p=g.join(s),T=0,y=p.length-1;for(;T<=y;){let N=Math.floor((T+y)/2),R=p.slice(0,N+1);o(R)+f<=u?T=N+1:y=N-1}let k=p.slice(0,T);if(a)for(k=I(k);!k.length&&S.length;){let N=S.pop();l.pop(),N&&typeof N=="string"&&(k=I(N))}if(i&&e!==0){let{startFragment:N,endFragment:R}=se({end:e,lastLineText:k,fullText:b,targetWidth:u,ellipsisWidth:f,measureWidth:o});d=`${N}${R}`,x=le.createElement("span",null,N,n,R)}else d=k,x=le.createElement("span",null,k,n)}else{let p=0,T=g.length-1;for(;p<=T;){let y=Math.floor((p+T)/2),k=g.slice(0,y+1).join(s);o(k)<=u?p=y+1:T=y-1}if(p===0){c=r-1;continue}d=g.slice(0,p).join(s),x=d,h[0].splice(0,p)}S.push(x),l.push(d)}return{didTruncate:m,resultLines:S,visibleText:l.join(`
`),visibleTextLines:l}};var B=({children:n,ellipsis:t="\u2026",lines:e=1,trimWhitespace:r=!1,width:o=0,separator:i=" ",middle:s=!1,end:u=5,preserveMarkup:M=!1,onTruncate:a,...S})=>{let[l,b]=z(),[h,f]=z(),[m,c]=z(0),[g,x]=z(0),d=G(null),p=G(null),T=G(null);W(()=>{p&&p.current&&p.current.parentNode&&p.current.parentNode.removeChild(p.current)},[m]);let y=J(()=>{if(!d.current?.parentElement)return;let L=o||Math.floor(d.current.parentElement.getBoundingClientRect().width);if(!L)return window.requestAnimationFrame(()=>y());let E=window.getComputedStyle(d.current),C=[E.fontWeight,E.fontStyle,E.fontSize,E.fontFamily].join(" ");l&&(l.font=C,l.letterSpacing=E.letterSpacing),c(L)},[l,o]);W(()=>{let L=document.createElement("canvas");b(L.getContext("2d"))},[]),W(()=>(y(),window.addEventListener("resize",y),()=>{window.removeEventListener("resize",y),window.cancelAnimationFrame(g)}),[y,g]);let k=J(L=>{typeof a=="function"&&x(window.requestAnimationFrame(()=>{a(L)}))},[a]),N=J(L=>l?.measureText(L).width||0,[l]),R=q(()=>!Number.isSafeInteger(e)||e<0?0:e,[e]),H=q(()=>s?1:R,[R,s]),K=q(()=>{let L=Math.abs(u),E=Number.isFinite(L)?Math.floor(L):0;return E>0?-E:E},[u]);return W(()=>{let L=!!(d.current&&m);if(typeof window>"u"||!L)return;if(H<=0){f(n),k(!1);return}let E=ae({ellipsis:t,ellipsisRef:T.current,end:K,lines:H,measureWidth:N,middleTruncate:s,separator:i,targetWidth:m,textRef:p.current,trimWhitespace:r});if(M&&!s){let C=ne({fallbackDidTruncate:E.didTruncate,fallbackVisibleTextLines:E.visibleTextLines,ellipsis:t,ellipsisNode:T.current,lines:H,node:p.current,rootNode:d.current,separator:i,trimWhitespace:r});f(C.didTruncate?C.result:n),k(C.didTruncate);return}f(E.resultLines.map(oe)),k(E.didTruncate)},[n,t,K,H,N,s,M,i,m,r,k]),A.createElement("span",{...S,ref:d},A.createElement("span",null,h),A.createElement("span",{ref:p},n),A.createElement("span",{ref:T,style:{position:"fixed",visibility:"hidden",top:0,left:0}},t))};import ce,{forwardRef as Se}from"react";var ke=Se(({children:n,...t},e)=>{let{width:r,middle:o,lines:i,...s}=t;return ce.createElement("div",{ref:e,style:{width:"100%"}},ce.createElement(B,{...s,middle:!0},n))});ke.displayName="MiddleTruncate";import $,{forwardRef as Le,useCallback as pe,useImperativeHandle as we,useRef as Re,useState as Ce}from"react";import ue,{isValidElement as Ee,useMemo as Ne}from"react";var U=({type:n,label:t,className:e,toggleLines:r})=>{let o=Ne(()=>n==="more"?"\u2026 ":" ",[n]);return Ee(t)?t:ue.createElement("span",null,o,ue.createElement("a",{href:"#",className:e,onClick:r,"data-testid":`${n}-button`},t))};var ve=Le(({defaultExpanded:n=!1,expanded:t,lines:e=3,more:r="Expand",less:o="Collapse",anchorClass:i,onToggle:s,children:u,...M},a)=>{let{width:S,middle:l,end:b,ellipsis:h,...f}=M,[,m]=Ce({}),c=pe(()=>m({}),[]),g=Re(n),x=typeof t=="boolean",d=x?t:g.current,p=pe(T=>{T.preventDefault();let y=!d;x||(g.current=y,c()),s?.(y)},[d,x,s,c]);return we(a,()=>({toggleLines:p})),$.createElement("div",{style:{width:"100%"}},$.createElement(B,{...f,lines:d?0:e,ellipsis:$.createElement(U,{type:"more",label:r,className:i,toggleLines:p})},u),d&&$.createElement(U,{type:"less",label:o,className:i,toggleLines:p}))});ve.displayName="ShowMore";export{ke as MiddleTruncate,ve as ShowMore,B as Truncate};