@@ -2,6 +2,7 @@ import { Fragment, useEffect, useRef, useState, forwardRef, useImperativeHandle
22import styles from '@patternfly/react-styles/css/components/Truncate/truncate' ;
33import { css } from '@patternfly/react-styles' ;
44import { Tooltip , TooltipPosition , TooltipProps } from '../Tooltip' ;
5+ import { getReferenceElement } from '../../helpers' ;
56import { getResizeObserver } from '../../helpers/resizeObserver' ;
67import { debounce } from '../../helpers/util' ;
78
@@ -76,94 +77,82 @@ const TruncateBase: React.FunctionComponent<TruncateProps> = ({
7677 omissionContent = '\u2026' ,
7778 content,
7879 innerRef,
79- onMouseEnter,
80- onMouseLeave,
81- onFocus,
82- onBlur,
8380 ...props
8481} : TruncateProps ) => {
85- const [ isTruncated , setIsTruncated ] = useState ( false ) ;
82+ const [ isTruncated , setIsTruncated ] = useState ( true ) ;
83+ const [ parentElement , setParentElement ] = useState < HTMLElement > ( null ) ;
84+ const [ textElement , setTextElement ] = useState < HTMLElement > ( null ) ;
8685 const [ shouldRenderByMaxChars , setShouldRenderByMaxChars ] = useState ( maxCharsDisplayed > 0 ) ;
87- const [ showTooltip , setShowTooltip ] = useState ( false ) ;
8886
8987 const textRef = useRef < HTMLElement > ( null ) ;
9088 useImperativeHandle ( innerRef , ( ) => textRef . current ! ) ;
9189 const defaultSubParentRef = useRef < any > ( null ) ;
9290 const subParentRef = tooltipProps ?. triggerRef || defaultSubParentRef ;
91+ const observer = useRef ( null ) ;
9392
9493 if ( maxCharsDisplayed <= 0 ) {
9594 // eslint-disable-next-line no-console
9695 console . warn ( 'Truncate: the maxCharsDisplayed must be greater than 0, otherwise no content will be visible.' ) ;
9796 }
9897
99- useEffect ( ( ) => {
100- if ( shouldRenderByMaxChars ) {
101- setIsTruncated ( content . length > maxCharsDisplayed ) ;
102- }
103- } , [ shouldRenderByMaxChars , content . length , maxCharsDisplayed ] ) ;
98+ const getActualWidth = ( element : Element ) => {
99+ const computedStyle = getComputedStyle ( element ) ;
104100
105- useEffect ( ( ) => {
106- setShouldRenderByMaxChars ( maxCharsDisplayed > 0 ) ;
107- } , [ maxCharsDisplayed ] ) ;
101+ return (
102+ parseFloat ( computedStyle . width ) -
103+ parseFloat ( computedStyle . paddingLeft ) -
104+ parseFloat ( computedStyle . paddingRight ) -
105+ parseFloat ( computedStyle . borderRight ) -
106+ parseFloat ( computedStyle . borderLeft )
107+ ) ;
108+ } ;
108109
109- // Check truncation on mount for without maxChars
110- useEffect ( ( ) => {
111- if ( ! shouldRenderByMaxChars && textRef . current ) {
112- setIsTruncated ( textRef . current . scrollWidth > textRef . current . clientWidth ) ;
113- }
114- } , [ shouldRenderByMaxChars , content ] ) ;
110+ const calculateTotalTextWidth = ( element : Element , trailingNumChars : number , content : string ) => {
111+ const firstTextWidth = element . scrollWidth ;
112+ const firstTextLength = content . length ;
113+ return ( firstTextWidth / firstTextLength ) * trailingNumChars + firstTextWidth ;
114+ } ;
115115
116- const debouncedHandleResize = debounce ( ( ) => {
117- if ( ! shouldRenderByMaxChars && textRef . current ) {
118- const isCurrentlyTruncated = textRef . current . scrollWidth > textRef . current . clientWidth ;
119- setIsTruncated ( isCurrentlyTruncated ) ;
116+ useEffect ( ( ) => {
117+ if ( textRef && textRef . current && ! textElement ) {
118+ setTextElement ( textRef . current ) ;
120119 }
121- } , 500 ) ;
120+ } , [ textRef , textElement ] ) ;
122121
123- // Set up ResizeObserver for non-maxChars truncation
124122 useEffect ( ( ) => {
125- if ( ! shouldRenderByMaxChars && textRef . current ) {
126- const observer = getResizeObserver ( textRef . current , debouncedHandleResize , false ) ;
127- return observer ;
123+ const refElement = getReferenceElement ( subParentRef ) ;
124+ if ( refElement ?. parentElement && ! parentElement ) {
125+ setParentElement ( refElement . parentElement ) ;
128126 }
129- } , [ shouldRenderByMaxChars , debouncedHandleResize ] ) ;
127+ } , [ subParentRef , parentElement ] ) ;
130128
131- // Check if content is truncated (called on hover/focus)
132- const checkTruncation = ( ) : boolean => {
133- if ( shouldRenderByMaxChars ) {
134- return isTruncated ;
135- }
129+ useEffect ( ( ) => {
130+ if ( textElement && parentElement && ! observer . current && ! shouldRenderByMaxChars ) {
131+ const totalTextWidth = calculateTotalTextWidth ( textElement , trailingNumChars , content ) ;
132+ const textWidth = position === 'middle' ? totalTextWidth : textElement . scrollWidth ;
136133
137- if ( ! textRef . current ) {
138- return false ;
139- }
134+ const debouncedHandleResize = debounce ( ( ) => {
135+ const parentWidth = getActualWidth ( parentElement ) ;
136+ setIsTruncated ( textWidth >= parentWidth ) ;
137+ } , 500 ) ;
140138
141- return textRef . current . scrollWidth > textRef . current . clientWidth ;
142- } ;
139+ const observer = getResizeObserver ( parentElement , debouncedHandleResize ) ;
143140
144- const handleMouseEnter = ( e : React . MouseEvent < HTMLElement > ) => {
145- if ( checkTruncation ( ) ) {
146- setShowTooltip ( true ) ;
141+ return ( ) => {
142+ observer ( ) ;
143+ } ;
147144 }
148- onMouseEnter ?.( e ) ;
149- } ;
150-
151- const handleMouseLeave = ( e : React . MouseEvent < HTMLElement > ) => {
152- setShowTooltip ( false ) ;
153- onMouseLeave ?.( e ) ;
154- } ;
145+ } , [ textElement , parentElement , trailingNumChars , content , position , shouldRenderByMaxChars ] ) ;
155146
156- const handleFocus = ( e : React . FocusEvent < HTMLElement > ) => {
157- if ( checkTruncation ( ) ) {
158- setShowTooltip ( true ) ;
147+ useEffect ( ( ) => {
148+ if ( shouldRenderByMaxChars ) {
149+ setIsTruncated ( content . length > maxCharsDisplayed ) ;
159150 }
160- onFocus ?.( e ) ;
161- } ;
151+ } , [ shouldRenderByMaxChars , content . length , maxCharsDisplayed ] ) ;
162152
163- const handleBlur = ( e : React . FocusEvent < HTMLElement > ) => {
164- setShowTooltip ( false ) ;
165- onBlur ?.( e ) ;
166- } ;
153+ useEffect ( ( ) => {
154+ setShouldRenderByMaxChars ( maxCharsDisplayed > 0 ) ;
155+ } , [ maxCharsDisplayed ] ) ;
167156
168157 const lrmEntity = < Fragment > ‎</ Fragment > ;
169158 const isStartPosition = position === TruncatePosition . start ;
@@ -252,10 +241,6 @@ const TruncateBase: React.FunctionComponent<TruncateProps> = ({
252241 href = { href }
253242 className = { css ( styles . truncate , shouldRenderByMaxChars && styles . modifiers . fixed , className ) }
254243 { ...( isTruncated && ! href && ! tooltipProps ?. triggerRef && { tabIndex : 0 } ) }
255- onMouseEnter = { handleMouseEnter }
256- onMouseLeave = { handleMouseLeave }
257- onFocus = { handleFocus }
258- onBlur = { handleBlur }
259244 { ...props }
260245 >
261246 { ! shouldRenderByMaxChars ? renderResizeObserverContent ( ) : renderMaxDisplayContent ( ) }
@@ -264,14 +249,15 @@ const TruncateBase: React.FunctionComponent<TruncateProps> = ({
264249
265250 return (
266251 < >
267- < Tooltip
268- position = { tooltipPosition }
269- content = { content }
270- triggerRef = { subParentRef }
271- trigger = "manual"
272- isVisible = { showTooltip }
273- { ...tooltipProps }
274- />
252+ { isTruncated && (
253+ < Tooltip
254+ hidden = { ! isTruncated }
255+ position = { tooltipPosition }
256+ content = { content }
257+ triggerRef = { subParentRef }
258+ { ...tooltipProps }
259+ />
260+ ) }
275261 { truncateBody }
276262 </ >
277263 ) ;
0 commit comments