1- import { createSignal } from 'solid-js'
1+ import { createEffect , createSignal } from 'solid-js'
22import { useStyles } from '../../styles/use-styles'
33import { useHeadChanges } from '../../hooks/use-head-changes'
44import { Section , SectionDescription } from '@tanstack/devtools-ui'
55
6+ const TITLE_MAX_WIDTH_PX = 600
7+ const DESCRIPTION_MAX_WIDTH_PX = 960
8+ const ELLIPSIS = '...'
9+
10+ function truncateToWidth (
11+ el : HTMLDivElement ,
12+ text : string ,
13+ maxPx : number ,
14+ ) : string {
15+ el . textContent = text
16+ if ( el . offsetWidth <= maxPx ) return text
17+ for ( let i = text . length - 1 ; i >= 0 ; i -- ) {
18+ el . textContent = text . slice ( 0 , i ) + ELLIPSIS
19+ if ( el . offsetWidth <= maxPx ) return text . slice ( 0 , i ) + ELLIPSIS
20+ }
21+ return ELLIPSIS
22+ }
23+
624type SerpData = {
725 title : string
826 description : string
@@ -50,12 +68,48 @@ function getSerpFromHead(): SerpData {
5068
5169export function SerpPreviewSection ( ) {
5270 const [ serp , setSerp ] = createSignal < SerpData > ( getSerpFromHead ( ) )
71+ const [ titleOverflow , setTitleOverflow ] = createSignal ( false )
72+ const [ descriptionOverflow , setDescriptionOverflow ] = createSignal ( false )
73+ const [ displayTitle , setDisplayTitle ] = createSignal ( '' )
74+ const [ displayDescription , setDisplayDescription ] = createSignal ( '' )
75+ const [ titleMeasureEl , setTitleMeasureEl ] = createSignal <
76+ HTMLDivElement | undefined
77+ > ( undefined )
78+ const [ descMeasureEl , setDescMeasureEl ] = createSignal <
79+ HTMLDivElement | undefined
80+ > ( undefined )
5381 const styles = useStyles ( )
5482
5583 useHeadChanges ( ( ) => {
5684 setSerp ( getSerpFromHead ( ) )
5785 } )
5886
87+ createEffect ( ( ) => {
88+ const titleEl = titleMeasureEl ( )
89+ const descEl = descMeasureEl ( )
90+ const data = serp ( )
91+ if ( ! titleEl || ! descEl ) return
92+
93+ const titleText = data . title || 'No title'
94+ const descText = data . description || 'No meta description.'
95+
96+ const truncatedTitle = truncateToWidth (
97+ titleEl ,
98+ titleText ,
99+ TITLE_MAX_WIDTH_PX ,
100+ )
101+ setDisplayTitle ( truncatedTitle )
102+ setTitleOverflow ( truncatedTitle !== titleText )
103+
104+ const truncatedDesc = truncateToWidth (
105+ descEl ,
106+ descText ,
107+ DESCRIPTION_MAX_WIDTH_PX ,
108+ )
109+ setDisplayDescription ( truncatedDesc )
110+ setDescriptionOverflow ( truncatedDesc !== descText )
111+ } )
112+
59113 const data = serp ( )
60114
61115 return (
@@ -81,12 +135,38 @@ export function SerpPreviewSection() {
81135 </ div >
82136 </ div >
83137 < div class = { styles ( ) . serpSnippetTitle } >
84- { data . title || 'No title' }
138+ { displayTitle ( ) || data . title || 'No title' }
85139 </ div >
140+ < div
141+ ref = { setTitleMeasureEl }
142+ class = { `${ styles ( ) . serpSnippetTitle } ${ styles ( ) . serpMeasureHidden } ` }
143+ aria-hidden = "true"
144+ />
86145 < div class = { styles ( ) . serpSnippetDesc } >
87- { data . description || 'No meta description.' }
146+ { displayDescription ( ) || data . description || 'No meta description.' }
88147 </ div >
148+ < div
149+ ref = { setDescMeasureEl }
150+ class = { `${ styles ( ) . serpSnippetDesc } ${ styles ( ) . serpMeasureHidden } ` }
151+ aria-hidden = "true"
152+ />
89153 </ div >
154+ { ( titleOverflow ( ) || descriptionOverflow ( ) ) && (
155+ < div class = { styles ( ) . serpReportSection } >
156+ { titleOverflow ( ) && (
157+ < div class = { styles ( ) . serpReportItem } >
158+ The title is wider than 600px and it may not be displayed in full
159+ length.
160+ </ div >
161+ ) }
162+ { descriptionOverflow ( ) && (
163+ < div class = { styles ( ) . serpReportItem } >
164+ The meta description may get trimmed at ~960 pixels on desktop
165+ and at ~680px on mobile. Keep it below ~158 characters.
166+ </ div >
167+ ) }
168+ </ div >
169+ ) }
90170 </ Section >
91171 )
92172}
0 commit comments