diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index ccdb5b9e375951..8db908636df9c1 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -395,6 +395,45 @@ _Returns_ - `?Object`: Color object included in the colors array whose color property equals colorValue. Returns undefined if no color object matches this requirement. +### getComputedFluidTypographyValue + +Computes a fluid font-size value that uses clamp(). A minimum and maxinmum +font size OR a single font size can be specified. + +If a single font size is specified, it is scaled up and down by +minimumFontSizeFactor and maximumFontSizeFactor to arrive at the minimum and +maximum sizes. + +_Usage_ + +```js +// Calculate fluid font-size value from a minimum and maximum value. +const fontSize = getComputedFluidTypographyValue( { + minimumFontSize: '20px', + maximumFontSize: '45px', +} ); +// Calculate fluid font-size value from a single font size. +const fontSize = getComputedFluidTypographyValue( { + fontSize: '30px', +} ); +``` + +_Parameters_ + +- _args_ `Object`: +- _args.minimumViewPortWidth_ `?string`: Minimum viewport size from which type will have fluidity. Optional if fontSize is specified. +- _args.maximumViewPortWidth_ `?string`: Maximum size up to which type will have fluidity. Optional if fontSize is specified. +- _args.fontSize_ `?string`: Size to derive maximumFontSize and minimumFontSize from, if necessary. Optional if minimumFontSize and maximumFontSize are specified. +- _args.maximumFontSize_ `?string`: Maximum font size for any clamp() calculation. Optional. +- _args.minimumFontSize_ `?string`: Minimum font size for any clamp() calculation. Optional. +- _args.scaleFactor_ `?number`: A scale factor to determine how fast a font scales within boundaries. Optional. +- _args.minimumFontSizeFactor_ `?number`: How much to scale defaultFontSize by to derive minimumFontSize. Optional. +- _args.maximumFontSizeFactor_ `?number`: How much to scale defaultFontSize by to derive maximumFontSize. Optional. + +_Returns_ + +- `string|null`: A font-size value using clamp(). + ### getFontSize Returns the font size object based on an array of named font sizes and the namedFontSize and customFontSize values. diff --git a/packages/block-editor/src/components/font-sizes/fluid-utils.js b/packages/block-editor/src/components/font-sizes/fluid-utils.js new file mode 100644 index 00000000000000..ea69585853586c --- /dev/null +++ b/packages/block-editor/src/components/font-sizes/fluid-utils.js @@ -0,0 +1,218 @@ +/** + * The fluid utilities must match the backend equivalent. + * See: gutenberg_get_typography_font_size_value() in lib/block-supports/typography.php + * --------------------------------------------------------------- + */ + +// Defaults. +const DEFAULT_MAXIMUM_VIEWPORT_WIDTH = '1600px'; +const DEFAULT_MINIMUM_VIEWPORT_WIDTH = '768px'; +const DEFAULT_SCALE_FACTOR = 1; +const DEFAULT_MINIMUM_FONT_SIZE_FACTOR = 0.75; +const DEFAULT_MAXIMUM_FONT_SIZE_FACTOR = 1.5; + +/** + * Computes a fluid font-size value that uses clamp(). A minimum and maxinmum + * font size OR a single font size can be specified. + * + * If a single font size is specified, it is scaled up and down by + * minimumFontSizeFactor and maximumFontSizeFactor to arrive at the minimum and + * maximum sizes. + * + * @example + * ```js + * // Calculate fluid font-size value from a minimum and maximum value. + * const fontSize = getComputedFluidTypographyValue( { + * minimumFontSize: '20px', + * maximumFontSize: '45px' + * } ); + * // Calculate fluid font-size value from a single font size. + * const fontSize = getComputedFluidTypographyValue( { + * fontSize: '30px', + * } ); + * ``` + * + * @param {Object} args + * @param {?string} args.minimumViewPortWidth Minimum viewport size from which type will have fluidity. Optional if fontSize is specified. + * @param {?string} args.maximumViewPortWidth Maximum size up to which type will have fluidity. Optional if fontSize is specified. + * @param {?string} args.fontSize Size to derive maximumFontSize and minimumFontSize from, if necessary. Optional if minimumFontSize and maximumFontSize are specified. + * @param {?string} args.maximumFontSize Maximum font size for any clamp() calculation. Optional. + * @param {?string} args.minimumFontSize Minimum font size for any clamp() calculation. Optional. + * @param {?number} args.scaleFactor A scale factor to determine how fast a font scales within boundaries. Optional. + * @param {?number} args.minimumFontSizeFactor How much to scale defaultFontSize by to derive minimumFontSize. Optional. + * @param {?number} args.maximumFontSizeFactor How much to scale defaultFontSize by to derive maximumFontSize. Optional. + * + * @return {string|null} A font-size value using clamp(). + */ +export function getComputedFluidTypographyValue( { + minimumFontSize, + maximumFontSize, + fontSize, + minimumViewPortWidth = DEFAULT_MINIMUM_VIEWPORT_WIDTH, + maximumViewPortWidth = DEFAULT_MAXIMUM_VIEWPORT_WIDTH, + scaleFactor = DEFAULT_SCALE_FACTOR, + minimumFontSizeFactor = DEFAULT_MINIMUM_FONT_SIZE_FACTOR, + maximumFontSizeFactor = DEFAULT_MAXIMUM_FONT_SIZE_FACTOR, +} ) { + // Calculate missing minimumFontSize and maximumFontSize from + // defaultFontSize if provided. + if ( fontSize && ( ! minimumFontSize || ! maximumFontSize ) ) { + // Parse default font size. + const fontSizeParsed = getTypographyValueAndUnit( fontSize ); + + // Protect against invalid units. + if ( ! fontSizeParsed?.unit ) { + return null; + } + + // If no minimumFontSize is provided, derive using min scale factor. + if ( ! minimumFontSize ) { + minimumFontSize = + fontSizeParsed.value * minimumFontSizeFactor + + fontSizeParsed.unit; + } + + // If no maximumFontSize is provided, derive using max scale factor. + if ( ! maximumFontSize ) { + maximumFontSize = + fontSizeParsed.value * maximumFontSizeFactor + + fontSizeParsed.unit; + } + } + + // Return early if one of the provided inputs is not provided. + if ( ! minimumFontSize || ! maximumFontSize ) { + return null; + } + + // Grab the minimum font size and normalize it in order to use the value for calculations. + const minimumFontSizeParsed = getTypographyValueAndUnit( minimumFontSize ); + + // We get a 'preferred' unit to keep units consistent when calculating, + // otherwise the result will not be accurate. + const fontSizeUnit = minimumFontSizeParsed?.unit || 'rem'; + + // Grab the maximum font size and normalize it in order to use the value for calculations. + const maximumFontSizeParsed = getTypographyValueAndUnit( maximumFontSize, { + coerceTo: fontSizeUnit, + } ); + + // Protect against unsupported units. + if ( ! minimumFontSizeParsed || ! maximumFontSizeParsed ) { + return null; + } + + // Use rem for accessible fluid target font scaling. + const minimumFontSizeRem = getTypographyValueAndUnit( minimumFontSize, { + coerceTo: 'rem', + } ); + + // Viewport widths defined for fluid typography. Normalize units + const maximumViewPortWidthParsed = getTypographyValueAndUnit( + maximumViewPortWidth, + { coerceTo: fontSizeUnit } + ); + const minumumViewPortWidthParsed = getTypographyValueAndUnit( + minimumViewPortWidth, + { coerceTo: fontSizeUnit } + ); + + // Protect against unsupported units. + if ( + ! maximumViewPortWidthParsed || + ! minumumViewPortWidthParsed || + ! minimumFontSizeRem + ) { + return null; + } + + // Build CSS rule. + // Borrowed from https://websemantics.uk/tools/responsive-font-calculator/. + const minViewPortWidthOffsetValue = roundToPrecision( + minumumViewPortWidthParsed.value / 100, + 3 + ); + + const viewPortWidthOffset = minViewPortWidthOffsetValue + fontSizeUnit; + let linearFactor = + 100 * + ( ( maximumFontSizeParsed.value - minimumFontSizeParsed.value ) / + ( maximumViewPortWidthParsed.value - + minumumViewPortWidthParsed.value ) ); + linearFactor = roundToPrecision( linearFactor, 3 ) || 1; + const linearFactorScaled = linearFactor * scaleFactor; + const fluidTargetFontSize = `${ minimumFontSizeRem.value }${ minimumFontSizeRem.unit } + ((1vw - ${ viewPortWidthOffset }) * ${ linearFactorScaled })`; + + return `clamp(${ minimumFontSize }, ${ fluidTargetFontSize }, ${ maximumFontSize })`; +} + +/** + * + * @param {string} rawValue Raw size value from theme.json. + * @param {Object|undefined} options Calculation options. + * + * @return {{ unit: string, value: number }|null} An object consisting of `'value'` and `'unit'` properties. + */ +export function getTypographyValueAndUnit( rawValue, options = {} ) { + if ( ! rawValue ) { + return null; + } + + if ( typeof rawValue === 'number' && ! Number.isNaN( rawValue ) ) { + rawValue = `${ rawValue }px`; + } + + const { coerceTo, rootSizeValue, acceptableUnits } = { + coerceTo: '', + // Default browser font size. Later we could inject some JS to compute this `getComputedStyle( document.querySelector( "html" ) ).fontSize`. + rootSizeValue: 16, + acceptableUnits: [ 'rem', 'px', 'em' ], + ...options, + }; + + const acceptableUnitsGroup = acceptableUnits?.join( '|' ); + const regexUnits = new RegExp( + `^(\\d*\\.?\\d+)(${ acceptableUnitsGroup }){1,1}$` + ); + + const matches = rawValue.match( regexUnits ); + + // We need a number value and a unit. + if ( ! matches || matches.length < 3 ) { + return null; + } + + let [ , value, unit ] = matches; + + let returnValue = parseFloat( value ); + + if ( 'px' === coerceTo && ( 'em' === unit || 'rem' === unit ) ) { + returnValue = returnValue * rootSizeValue; + unit = coerceTo; + } + + if ( 'px' === unit && ( 'em' === coerceTo || 'rem' === coerceTo ) ) { + returnValue = returnValue / rootSizeValue; + unit = coerceTo; + } + + return { + value: returnValue, + unit, + }; +} + +/** + * Returns a value rounded to defined precision. + * Returns `undefined` if the value is not a valid finite number. + * + * @param {number} value Raw value. + * @param {number} digits The number of digits to appear after the decimal point + * + * @return {number|undefined} Value rounded to standard precision. + */ +export function roundToPrecision( value, digits = 3 ) { + return Number.isFinite( value ) + ? parseFloat( value.toFixed( digits ) ) + : undefined; +} diff --git a/packages/block-editor/src/components/font-sizes/index.js b/packages/block-editor/src/components/font-sizes/index.js index 55ef51a02aa4d5..1702868f576816 100644 --- a/packages/block-editor/src/components/font-sizes/index.js +++ b/packages/block-editor/src/components/font-sizes/index.js @@ -3,5 +3,6 @@ export { getFontSizeClass, getFontSizeObjectByValue, } from './utils'; +export { getComputedFluidTypographyValue } from './fluid-utils'; export { default as FontSizePicker } from './font-size-picker'; export { default as withFontSizes } from './with-font-sizes'; diff --git a/packages/block-editor/src/components/font-sizes/test/fluid-utils.js b/packages/block-editor/src/components/font-sizes/test/fluid-utils.js new file mode 100644 index 00000000000000..a023a2826f5e4a --- /dev/null +++ b/packages/block-editor/src/components/font-sizes/test/fluid-utils.js @@ -0,0 +1,77 @@ +/** + * WordPress dependencies + */ +import { logged } from '@wordpress/deprecated'; + +/** + * Internal dependencies + */ +import { getComputedFluidTypographyValue } from '../fluid-utils'; + +describe( 'getComputedFluidTypographyValue()', () => { + afterEach( () => { + for ( const key in logged ) { + delete logged[ key ]; + } + } ); + + it( 'should return a fluid font size when given a min and max font size', () => { + const fluidTypographyValues = getComputedFluidTypographyValue( { + minimumFontSize: '20px', + maximumFontSize: '45px', + } ); + expect( fluidTypographyValues ).toBe( + 'clamp(20px, 1.25rem + ((1vw - 7.68px) * 3.005), 45px)' + ); + } ); + + it( 'should return a fluid font size when given a font size', () => { + const fluidTypographyValues = getComputedFluidTypographyValue( { + fontSize: '30px', + } ); + expect( fluidTypographyValues ).toBe( + 'clamp(22.5px, 1.40625rem + ((1vw - 7.68px) * 2.704), 45px)' + ); + } ); + + it( 'should return a fluid font size based on px when given a numerical font size', () => { + const fluidTypographyValues = getComputedFluidTypographyValue( { + fontSize: '30px', + } ); + expect( fluidTypographyValues ).toBe( + 'clamp(22.5px, 1.40625rem + ((1vw - 7.68px) * 2.704), 45px)' + ); + } ); + + it( 'should return a fluid font size when given a min and max viewport width', () => { + const fluidTypographyValues = getComputedFluidTypographyValue( { + fontSize: '30px', + minimumViewPortWidth: '500px', + maximumViewPortWidth: '1000px', + } ); + expect( fluidTypographyValues ).toBe( + 'clamp(22.5px, 1.40625rem + ((1vw - 5px) * 4.5), 45px)' + ); + } ); + + it( 'should return a fluid font size when given a scale factor', () => { + const fluidTypographyValues = getComputedFluidTypographyValue( { + fontSize: '30px', + scaleFactor: '2', + } ); + expect( fluidTypographyValues ).toBe( + 'clamp(22.5px, 1.40625rem + ((1vw - 7.68px) * 5.408), 45px)' + ); + } ); + + it( 'should return a fluid font size when given a min and max font size factor', () => { + const fluidTypographyValues = getComputedFluidTypographyValue( { + fontSize: '30px', + minimumFontSizeFactor: '0.5', + maximumFontSizeFactor: '2', + } ); + expect( fluidTypographyValues ).toBe( + 'clamp(15px, 0.9375rem + ((1vw - 7.68px) * 5.409), 60px)' + ); + } ); +} ); diff --git a/packages/block-editor/src/hooks/font-size.js b/packages/block-editor/src/hooks/font-size.js index 40abdd8f8eb302..f8fa94538f53ac 100644 --- a/packages/block-editor/src/hooks/font-size.js +++ b/packages/block-editor/src/hooks/font-size.js @@ -5,6 +5,7 @@ import { addFilter } from '@wordpress/hooks'; import { hasBlockSupport } from '@wordpress/blocks'; import TokenList from '@wordpress/token-list'; import { createHigherOrderComponent } from '@wordpress/compose'; +import { select } from '@wordpress/data'; /** * Internal dependencies @@ -14,6 +15,7 @@ import { getFontSizeClass, getFontSizeObjectByValue, FontSizePicker, + getComputedFluidTypographyValue, } from '../components/font-sizes'; import { TYPOGRAPHY_SUPPORT_KEY } from './typography'; import { @@ -22,6 +24,7 @@ import { shouldSkipSerialization, } from './utils'; import useSetting from '../components/use-setting'; +import { store as blockEditorStore } from '../store'; export const FONT_SIZE_SUPPORT_KEY = 'typography.fontSize'; @@ -282,6 +285,69 @@ export function addTransforms( result, source, index, results ) { ); } +/** + * Allow custom font sizes to appear fluid when fluid typography is enabled at + * the theme level. + * + * Adds a custom getEditWrapperProps() callback to all block types that support + * font sizes. Then, if fluid typography is enabled, this callback will swap any + * custom font size in style.fontSize with a fluid font size (i.e. one that uses + * clamp()). + * + * It's important that this hook runs after 'core/style/addEditProps' sets + * style.fontSize as otherwise fontSize will be overwritten. + * + * @param {Object} blockType Block settings object. + */ +function addEditPropsForFluidCustomFontSizes( blockType ) { + if ( + ! hasBlockSupport( blockType, FONT_SIZE_SUPPORT_KEY ) || + shouldSkipSerialization( blockType, TYPOGRAPHY_SUPPORT_KEY, 'fontSize' ) + ) { + return blockType; + } + + const existingGetEditWrapperProps = blockType.getEditWrapperProps; + + blockType.getEditWrapperProps = ( attributes ) => { + const wrapperProps = existingGetEditWrapperProps + ? existingGetEditWrapperProps( attributes ) + : {}; + + const fontSize = wrapperProps?.style?.fontSize; + + // TODO: This sucks! We should be using useSetting( 'typography.fluid' ) + // or even useSelect( blockEditorStore ). We can't do either here + // because getEditWrapperProps is a plain JavaScript function called by + // BlockListBlock and not a React component rendered within + // BlockListContext.Provider. If we set fontSize using editor. + // BlockListBlock instead of using getEditWrapperProps then the value is + // clobbered when the core/style/addEditProps filter runs. + const isFluidTypographyEnabled = + !! select( blockEditorStore ).getSettings().__experimentalFeatures + ?.typography?.fluid; + + const newFontSize = + fontSize && isFluidTypographyEnabled + ? getComputedFluidTypographyValue( { fontSize } ) + : null; + + if ( newFontSize === null ) { + return wrapperProps; + } + + return { + ...wrapperProps, + style: { + ...wrapperProps?.style, + fontSize: newFontSize, + }, + }; + }; + + return blockType; +} + addFilter( 'blocks.registerBlockType', 'core/font/addAttribute', @@ -307,3 +373,12 @@ addFilter( 'core/font-size/addTransforms', addTransforms ); + +addFilter( + 'blocks.registerBlockType', + 'core/font-size/addEditPropsForFluidCustomFontSizes', + addEditPropsForFluidCustomFontSizes, + // Run after 'core/style/addEditProps' so that the style object has already + // been translated into inline CSS. + 11 +); diff --git a/packages/edit-site/src/components/global-styles/test/typography-utils.js b/packages/edit-site/src/components/global-styles/test/typography-utils.js index cbc0fe53a3390d..c70a1e4050e930 100644 --- a/packages/edit-site/src/components/global-styles/test/typography-utils.js +++ b/packages/edit-site/src/components/global-styles/test/typography-utils.js @@ -15,6 +15,14 @@ describe( 'typography utils', () => { typographySettings: undefined, expected: '28px', }, + // Default return non-fluid value where `size` is not a number | string. + { + preset: { + size: undefined, + }, + typographySettings: undefined, + expected: undefined, + }, // Should return non-fluid value when fluid is `false`. { preset: { @@ -26,6 +34,17 @@ describe( 'typography utils', () => { }, expected: '28px', }, + // Should coerce number to `px` and return non-fluid value. + { + preset: { + size: 33, + }, + typographySettings: { + fluid: true, + }, + expected: + 'clamp(24.75px, 1.546875rem + ((1vw - 7.68px) * 2.975), 49.5px)', + }, // Should return fluid value. { preset: { diff --git a/packages/edit-site/src/components/global-styles/typography-utils.js b/packages/edit-site/src/components/global-styles/typography-utils.js index 464021dfa0b9fc..1f54549a2c278c 100644 --- a/packages/edit-site/src/components/global-styles/typography-utils.js +++ b/packages/edit-site/src/components/global-styles/typography-utils.js @@ -4,6 +4,11 @@ * --------------------------------------------------------------- */ +/** + * WordPress dependencies + */ +import { getComputedFluidTypographyValue } from '@wordpress/block-editor'; + /** * Returns a font-size value based on a given font-size preset. * Takes into account fluid typography parameters and attempts to return a css formula depending on available, valid values. @@ -27,50 +32,15 @@ export function getTypographyFontSizeValue( preset, typographySettings ) { return defaultSize; } - // Defaults. - const DEFAULT_MAXIMUM_VIEWPORT_WIDTH = '1600px'; - const DEFAULT_MINIMUM_VIEWPORT_WIDTH = '768px'; - const DEFAULT_MINIMUM_FONT_SIZE_FACTOR = 0.75; - const DEFAULT_MAXIMUM_FONT_SIZE_FACTOR = 1.5; - const DEFAULT_SCALE_FACTOR = 1; - - // Font sizes. // A font size has explicitly bypassed fluid calculations. if ( false === preset?.fluid ) { return defaultSize; } - const fluidFontSizeSettings = preset?.fluid || {}; - - // Try to grab explicit min and max fluid font sizes. - let minimumFontSizeRaw = fluidFontSizeSettings?.min; - let maximumFontSizeRaw = fluidFontSizeSettings?.max; - const preferredSize = getTypographyValueAndUnit( defaultSize ); - - // Protect against unsupported units. - if ( ! preferredSize?.unit ) { - return defaultSize; - } - - // If no fluid min or max font sizes are available, create some using min/max font size factors. - if ( ! minimumFontSizeRaw ) { - minimumFontSizeRaw = - preferredSize.value * DEFAULT_MINIMUM_FONT_SIZE_FACTOR + - preferredSize.unit; - } - - if ( ! maximumFontSizeRaw ) { - maximumFontSizeRaw = - preferredSize.value * DEFAULT_MAXIMUM_FONT_SIZE_FACTOR + - preferredSize.unit; - } - const fluidFontSizeValue = getComputedFluidTypographyValue( { - maximumViewPortWidth: DEFAULT_MAXIMUM_VIEWPORT_WIDTH, - minimumViewPortWidth: DEFAULT_MINIMUM_VIEWPORT_WIDTH, - maximumFontSize: maximumFontSizeRaw, - minimumFontSize: minimumFontSizeRaw, - scaleFactor: DEFAULT_SCALE_FACTOR, + minimumFontSize: preset?.fluid?.min, + maximumFontSize: preset?.fluid?.max, + fontSize: defaultSize, } ); if ( !! fluidFontSizeValue ) { @@ -79,150 +49,3 @@ export function getTypographyFontSizeValue( preset, typographySettings ) { return defaultSize; } - -/** - * Internal implementation of clamp() based on available min/max viewport width, and min/max font sizes. - * - * @param {Object} args - * @param {string} args.maximumViewPortWidth Maximum size up to which type will have fluidity. - * @param {string} args.minimumViewPortWidth Minimum viewport size from which type will have fluidity. - * @param {string} args.maximumFontSize Maximum font size for any clamp() calculation. - * @param {string} args.minimumFontSize Minimum font size for any clamp() calculation. - * @param {number} args.scaleFactor A scale factor to determine how fast a font scales within boundaries. - * - * @return {string|null} A font-size value using clamp(). - */ -export function getComputedFluidTypographyValue( { - maximumViewPortWidth, - minimumViewPortWidth, - maximumFontSize, - minimumFontSize, - scaleFactor, -} ) { - // Grab the minimum font size and normalize it in order to use the value for calculations. - const minimumFontSizeParsed = getTypographyValueAndUnit( minimumFontSize ); - - // We get a 'preferred' unit to keep units consistent when calculating, - // otherwise the result will not be accurate. - const fontSizeUnit = minimumFontSizeParsed?.unit || 'rem'; - - // Grab the maximum font size and normalize it in order to use the value for calculations. - const maximumFontSizeParsed = getTypographyValueAndUnit( maximumFontSize, { - coerceTo: fontSizeUnit, - } ); - - // Protect against unsupported units. - if ( ! minimumFontSizeParsed || ! maximumFontSizeParsed ) { - return null; - } - - // Use rem for accessible fluid target font scaling. - const minimumFontSizeRem = getTypographyValueAndUnit( minimumFontSize, { - coerceTo: 'rem', - } ); - - // Viewport widths defined for fluid typography. Normalize units - const maximumViewPortWidthParsed = getTypographyValueAndUnit( - maximumViewPortWidth, - { coerceTo: fontSizeUnit } - ); - const minumumViewPortWidthParsed = getTypographyValueAndUnit( - minimumViewPortWidth, - { coerceTo: fontSizeUnit } - ); - - // Protect against unsupported units. - if ( - ! maximumViewPortWidthParsed || - ! minumumViewPortWidthParsed || - ! minimumFontSizeRem - ) { - return null; - } - - // Build CSS rule. - // Borrowed from https://websemantics.uk/tools/responsive-font-calculator/. - const minViewPortWidthOffsetValue = roundToPrecision( - minumumViewPortWidthParsed.value / 100, - 3 - ); - - const viewPortWidthOffset = minViewPortWidthOffsetValue + fontSizeUnit; - let linearFactor = - 100 * - ( ( maximumFontSizeParsed.value - minimumFontSizeParsed.value ) / - ( maximumViewPortWidthParsed.value - - minumumViewPortWidthParsed.value ) ); - linearFactor = roundToPrecision( linearFactor, 3 ) || 1; - const linearFactorScaled = linearFactor * scaleFactor; - const fluidTargetFontSize = `${ minimumFontSizeRem.value }${ minimumFontSizeRem.unit } + ((1vw - ${ viewPortWidthOffset }) * ${ linearFactorScaled })`; - - return `clamp(${ minimumFontSize }, ${ fluidTargetFontSize }, ${ maximumFontSize })`; -} - -/** - * - * @param {string} rawValue Raw size value from theme.json. - * @param {Object|undefined} options Calculation options. - * - * @return {{ unit: string, value: number }|null} An object consisting of `'value'` and `'unit'` properties. - */ -export function getTypographyValueAndUnit( rawValue, options = {} ) { - if ( ! rawValue ) { - return null; - } - - const { coerceTo, rootSizeValue, acceptableUnits } = { - coerceTo: '', - // Default browser font size. Later we could inject some JS to compute this `getComputedStyle( document.querySelector( "html" ) ).fontSize`. - rootSizeValue: 16, - acceptableUnits: [ 'rem', 'px', 'em' ], - ...options, - }; - - const acceptableUnitsGroup = acceptableUnits?.join( '|' ); - const regexUnits = new RegExp( - `^(\\d*\\.?\\d+)(${ acceptableUnitsGroup }){1,1}$` - ); - - const matches = rawValue.match( regexUnits ); - - // We need a number value and a unit. - if ( ! matches || matches.length < 3 ) { - return null; - } - - let [ , value, unit ] = matches; - - let returnValue = parseFloat( value ); - - if ( 'px' === coerceTo && ( 'em' === unit || 'rem' === unit ) ) { - returnValue = returnValue * rootSizeValue; - unit = coerceTo; - } - - if ( 'px' === unit && ( 'em' === coerceTo || 'rem' === coerceTo ) ) { - returnValue = returnValue / rootSizeValue; - unit = coerceTo; - } - - return { - value: returnValue, - unit, - }; -} - -/** - * Returns a value rounded to defined precision. - * Returns `undefined` if the value is not a valid finite number. - * - * @param {number} value Raw value. - * @param {number} digits The number of digits to appear after the decimal point - * - * @return {number|undefined} Value rounded to standard precision. - */ -export function roundToPrecision( value, digits = 3 ) { - return Number.isFinite( value ) - ? parseFloat( value.toFixed( digits ) ) - : undefined; -}