@wordpress/block-editor
Version:
103 lines (89 loc) • 3.45 kB
JavaScript
/**
* Shared utility functions for fit text functionality.
* Uses callback-based approach for maximum code reuse between editor and frontend.
*/
/**
* Find optimal font size using simple binary search between 0-2400px.
*
* @param {HTMLElement} textElement The text element
* @param {Function} applyFontSize Function that receives font size in pixels
* @return {number} Optimal font size
*/
function findOptimalFontSize( textElement, applyFontSize ) {
const alreadyHasScrollableHeight =
textElement.scrollHeight > textElement.clientHeight;
let minSize = 0;
let maxSize = 2400;
let bestSize = minSize;
const computedStyle = window.getComputedStyle( textElement );
let paddingLeft = parseFloat( computedStyle.paddingLeft ) || 0;
let paddingRight = parseFloat( computedStyle.paddingRight ) || 0;
const range = document.createRange();
range.selectNodeContents( textElement );
let referenceElement = textElement;
const parentElement = textElement.parentElement;
if ( parentElement ) {
const parentElementComputedStyle =
window.getComputedStyle( parentElement );
if ( parentElementComputedStyle?.display === 'flex' ) {
referenceElement = parentElement;
paddingLeft +=
parseFloat( parentElementComputedStyle.paddingLeft ) || 0;
paddingRight +=
parseFloat( parentElementComputedStyle.paddingRight ) || 0;
}
}
let maxclientHeight = referenceElement.clientHeight;
while ( minSize <= maxSize ) {
const midSize = Math.floor( ( minSize + maxSize ) / 2 );
applyFontSize( midSize );
// When there is padding if the text overflows to the
// padding area, it should be considered overflowing.
// Use Range API to measure actual text content dimensions.
const rect = range.getBoundingClientRect();
const textWidth = rect.width;
// Check if text fits within the element's width and is not
// overflowing into the padding area.
const fitsWidth =
textElement.scrollWidth <= referenceElement.clientWidth &&
textWidth <=
referenceElement.clientWidth - paddingLeft - paddingRight;
// Check if text fits within the element's height.
const fitsHeight =
alreadyHasScrollableHeight ||
textElement.scrollHeight <= referenceElement.clientHeight ||
textElement.scrollHeight <= maxclientHeight;
// When there are calculated line heights, text may jump in height
// the available space may decrease while the font size decreases,
// making text not fit.
// We store a maximum reference height: the maximum reference element height that was observed
// during the loop to avoid issues with such jumps.
if ( referenceElement.clientHeight > maxclientHeight ) {
maxclientHeight = referenceElement.clientHeight;
}
if ( fitsWidth && fitsHeight ) {
bestSize = midSize;
minSize = midSize + 1;
} else {
maxSize = midSize - 1;
}
}
range.detach();
return bestSize;
}
/**
* Complete fit text optimization for a single text element.
* Handles the full flow using callbacks for font size application.
*
* @param {HTMLElement} textElement The text element (paragraph, heading, etc.)
* @param {Function} applyFontSize Function that receives font size in pixels (0 to clear, >0 to apply)
*/
export function optimizeFitText( textElement, applyFontSize ) {
if ( ! textElement ) {
return;
}
applyFontSize( 0 );
const optimalSize = findOptimalFontSize( textElement, applyFontSize );
applyFontSize( optimalSize );
return optimalSize;
}