From 07b1f982c141c546c0c92891ba90296bafc89962 Mon Sep 17 00:00:00 2001 From: Ramon Date: Tue, 11 Oct 2022 21:56:49 +1100 Subject: [PATCH] WP 6.1: backport fluid typography (#44868) * Fluid typography: convert server-side block support values (#44762) * For serverside rendered typography block support styles, covert font size values to fluid values in the inline styles if fluid typography is activated. Added unit tests * Add fluid font size to Nav Link * Add fluid typography to Search block * Fix fluid typography for Submenu block with open on click * Fix fluid typography for Page List block * Remove unnecessary parameter * Call esc_attr only once on the whole typography string Co-authored-by: tellthemachines * Fluid Typography: Fix bug in global styles where fluid clamp rules were not calculated for custom values (#44761) * Fluid Typography: Fix bug where fluid clamp rules were not calculated for custom global styles values * Add inline comments * Add tests for JS changes * Fluid Typography: ensure global styles preset fluid sizes are calculated correctly (#44791) * Forked https://github.com/WordPress/gutenberg/pull/44761 * Ensuring the font size picker select box shows the right labels * update comment. Typescript has beaten me. It's much more convenient to use getFontSizeOptions(), but I guess we'll have to refactor. * Adding comment about Typescript bug (YAY, it wasn't me being dumb with TS for once) * Added tests yo * Changeo loggo * Create a new FontSizeSelectOption return type for getSelectedOption to: 1. Clean up type changes in #44791 2. Make TS linter be quiet * Revert accidental changes to emptytheme * Revert type changes and other calamities * Remove additional size value from getToggleGroupOptions test as I believe it is no longer expected Co-authored-by: Ramon Co-authored-by: ramonjd * Fluid typography: convert font size inline style attributes to fluid values (#44764) * This commit ensures that custom font size values that appear in the style attribute of block content are converted to fluid typography (if activated) * Adding comments * Bail early if we don't find a custom font size * Adding tests yo * Updating PHP doc to describe incoming type of $raw_value (string|number) * Make custom font sizes appear fluid in the block editor when fluid typography is enabled (#44765) * Make custom font sizes appear fluid in the block editor when fluid typography is enabled * Add tests for fluid utils * update description * You shall not pass with a number, well, yes, but we'll coerce it to `px` and the tests shall pass nonetheless!!! Co-authored-by: Ben Dwyer Co-authored-by: ramonjd * Fluid typography: covert font size number values to pixels (#44807) * Converts incoming raw font size values to value + pixel to remain consistent with the fontsizepicker component. * Updating comments / docs * Fluid typography: ensure fontsizes are strings or integers (#44847) * Updating PHP doc to describe incoming type of $raw_value (string|int) This PR also harmonizes the JS checks And review comments from https://github.com/WordPress/gutenberg/pull/44807#pullrequestreview-1136360189 These changes have already been backported to Core in https://github.com/WordPress/wordpress-develop/pull/3428 * Update changelog Add extra test for floats Add i18n domain * Font sizes can be string|int|float Updated tests and JSDoc type * Expand tests for gutenberg_get_typography_value_and_unit Fix typo in CHANGELOG.md * Initial commit (#44852) - ensure that we convert fluid font sizes to fluid values in the editor for search block block supports - we do this by passing the getTypographyClassesAndStyles hook a flag to tell it whether to convert Updated CHANGELOG.md Added tests Co-authored-by: tellthemachines Co-authored-by: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Co-authored-by: Robert Anderson Co-authored-by: Ben Dwyer --- lib/block-supports/typography.php | 79 +++- .../wordpress-6.1/class-wp-theme-json-6-1.php | 12 + packages/block-editor/CHANGELOG.md | 9 + packages/block-editor/README.md | 40 ++ .../src/components/font-sizes/fluid-utils.js | 221 ++++++++++ .../src/components/font-sizes/index.js | 1 + .../components/font-sizes/test/fluid-utils.js | 168 ++++++++ packages/block-editor/src/hooks/font-size.js | 75 ++++ .../src/hooks/test/use-typography-props.js | 22 + .../src/hooks/use-typography-props.js | 21 +- .../src/navigation-link/index.php | 9 +- .../src/navigation-submenu/index.php | 9 +- .../block-library/src/page-list/index.php | 9 +- packages/block-library/src/search/edit.js | 7 +- packages/block-library/src/search/index.php | 24 +- packages/components/CHANGELOG.md | 1 + .../src/font-size-picker/test/utils.js | 10 +- .../components/src/font-size-picker/utils.js | 2 +- .../global-styles/test/typography-utils.js | 84 ++++ .../test/use-global-styles-output.js | 60 ++- .../global-styles/typography-panel.js | 17 +- .../global-styles/typography-utils.js | 229 ++-------- .../global-styles/use-global-styles-output.js | 16 + phpunit/block-supports/typography-test.php | 395 +++++++++++++++++- .../theme.json | 9 + 25 files changed, 1292 insertions(+), 237 deletions(-) create mode 100644 packages/block-editor/src/components/font-sizes/fluid-utils.js create mode 100644 packages/block-editor/src/components/font-sizes/test/fluid-utils.js create mode 100644 phpunit/data/themedir1/block-theme-child-with-fluid-typography/theme.json diff --git a/lib/block-supports/typography.php b/lib/block-supports/typography.php index 212b058f822d7..2126aaf847fde 100644 --- a/lib/block-supports/typography.php +++ b/lib/block-supports/typography.php @@ -108,7 +108,11 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) { if ( $has_font_size_support && ! $should_skip_font_size ) { $preset_font_size = array_key_exists( 'fontSize', $block_attributes ) ? "var:preset|font-size|{$block_attributes['fontSize']}" : null; $custom_font_size = isset( $block_attributes['style']['typography']['fontSize'] ) ? $block_attributes['style']['typography']['fontSize'] : null; - $typography_block_styles['fontSize'] = $preset_font_size ? $preset_font_size : $custom_font_size; + $typography_block_styles['fontSize'] = $preset_font_size ? $preset_font_size : gutenberg_get_typography_font_size_value( + array( + 'size' => $custom_font_size, + ) + ); } if ( $has_font_family_support && ! $should_skip_font_family ) { @@ -228,13 +232,42 @@ function gutenberg_typography_get_css_variable_inline_style( $attributes, $featu return sprintf( '%s:var(--wp--preset--%s--%s);', $css_property, $css_property, $slug ); } +/** + * Renders typography styles/content to the block wrapper. + * + * @param string $block_content Rendered block content. + * @param array $block Block object. + * @return string Filtered block content. + */ +function gutenberg_render_typography_support( $block_content, $block ) { + if ( ! isset( $block['attrs']['style']['typography']['fontSize'] ) ) { + return $block_content; + } + + $custom_font_size = $block['attrs']['style']['typography']['fontSize']; + $fluid_font_size = gutenberg_get_typography_font_size_value( array( 'size' => $custom_font_size ) ); + + /* + * Checks that $fluid_font_size does not match $custom_font_size, + * which means it's been mutated by the fluid font size functions. + */ + if ( ! empty( $fluid_font_size ) && $fluid_font_size !== $custom_font_size ) { + // Replaces the first instance of `font-size:$custom_font_size` with `font-size:$fluid_font_size`. + return preg_replace( '/font-size\s*:\s*' . preg_quote( $custom_font_size, '/' ) . '\s*;?/', 'font-size:' . esc_attr( $fluid_font_size ) . ';', $block_content, 1 ); + } + + return $block_content; + +} + /** * Internal method that checks a string for a unit and value and returns an array consisting of `'value'` and `'unit'`, e.g., [ '42', 'rem' ]. + * A raw font size of `value + unit` is expected. If the value is a number, it will convert to `value + 'px'`. * * @access private * - * @param string $raw_value Raw size value from theme.json. - * @param array $options { + * @param string|int|float $raw_value Raw size value from theme.json. + * @param array $options { * Optional. An associative array of options. Default is empty array. * * @type string $coerce_to Coerce the value to rem or px. Default `'rem'`. @@ -244,10 +277,25 @@ function gutenberg_typography_get_css_variable_inline_style( $attributes, $featu * @return array An array consisting of `'value'` and `'unit'` properties. */ function gutenberg_get_typography_value_and_unit( $raw_value, $options = array() ) { + if ( ! is_string( $raw_value ) && ! is_int( $raw_value ) && ! is_float( $raw_value ) ) { + _doing_it_wrong( + __FUNCTION__, + __( 'Raw size value must be a string, integer or a float.', 'gutenberg' ), + '6.1.0' + ); + return null; + } + + // Converts numeric values to pixel values by default. if ( empty( $raw_value ) ) { return null; } + // Converts numbers to pixel values by default. + if ( is_numeric( $raw_value ) ) { + $raw_value = $raw_value . 'px'; + } + $defaults = array( 'coerce_to' => '', 'root_size_value' => 16, @@ -368,15 +416,27 @@ function gutenberg_get_computed_fluid_typography_value( $args = array() ) { * @param array $preset { * Required. fontSizes preset value as seen in theme.json. * - * @type string $name Name of the font size preset. - * @type string $slug Kebab-case unique identifier for the font size preset. - * @type string $size CSS font-size value, including units where applicable. + * @type string $name Name of the font size preset. + * @type string $slug Kebab-case unique identifier for the font size preset. + * @type string|int|float $size CSS font-size value, including units where applicable. * } * @param bool $should_use_fluid_typography An override to switch fluid typography "on". Can be used for unit testing. Default is `false`. * - * @return string Font-size value. + * @return string|null Font-size value or `null` if a size is not passed in $preset. */ function gutenberg_get_typography_font_size_value( $preset, $should_use_fluid_typography = false ) { + if ( ! isset( $preset['size'] ) ) { + return null; + } + + /* + * Catches empty values and 0/'0'. + * Fluid calculations cannot be performed on 0. + */ + if ( empty( $preset['size'] ) ) { + return $preset['size']; + } + // Check if fluid font sizes are activated. $typography_settings = gutenberg_get_global_settings( array( 'typography' ) ); $should_use_fluid_typography = isset( $typography_settings['fluid'] ) && true === $typography_settings['fluid'] ? true : $should_use_fluid_typography; @@ -446,3 +506,8 @@ function gutenberg_get_typography_font_size_value( $preset, $should_use_fluid_ty 'apply' => 'gutenberg_apply_typography_support', ) ); + +if ( function_exists( 'wp_render_typography_support' ) ) { + remove_filter( 'render_block', 'wp_render_typography_support' ); +} +add_filter( 'render_block', 'gutenberg_render_typography_support', 10, 2 ); diff --git a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php index c4b6935142dc3..6d63556cb1e29 100644 --- a/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php +++ b/lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php @@ -967,6 +967,18 @@ protected static function compute_style_properties( $styles, $settings = array() continue; } + // Calculates fluid typography rules where available. + if ( 'font-size' === $css_property ) { + /* + * gutenberg_get_typography_font_size_value() will check + * if fluid typography has been activated and also + * whether the incoming value can be converted to a fluid value. + * Values that already have a "clamp()" function will not pass the test, + * and therefore the original $value will be returned. + */ + $value = gutenberg_get_typography_font_size_value( array( 'size' => $value ) ); + } + $declarations[] = array( 'name' => $css_property, 'value' => $value, diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index aabc43c6ea99c..cc14eb7d86789 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -2,6 +2,15 @@ ## Unreleased +### Bug Fix + +- `FontSizePicker`: Update fluid utils so that only string, floats and integers are treated as valid font sizes for the purposes of fluid typography ([#44847](https://github.com/WordPress/gutenberg/pull/44847)) +- `getTypographyClassesAndStyles()`: Ensure that font sizes are transformed into fluid values if fluid typography is activated ([#44852](https://github.com/WordPress/gutenberg/pull/44852)) + +## 10.2.0 (2022-10-05) + +## 10.1.0 (2022-09-21) + ## 10.0.0 (2022-09-13) ### Breaking change diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index ccdb5b9e37595..2ecacfd4b959e 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|number]`: 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. @@ -482,6 +521,7 @@ attributes. _Parameters_ - _attributes_ `Object`: Block attributes. +- _isFluidFontSizeActive_ `boolean`: Whether the function should try to convert font sizes to fluid values. _Returns_ 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 0000000000000..7294a83da106f --- /dev/null +++ b/packages/block-editor/src/components/font-sizes/fluid-utils.js @@ -0,0 +1,221 @@ +/** + * 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|number} [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 })`; +} + +/** + * Internal method that checks a string for a unit and value and returns an array consisting of `'value'` and `'unit'`, e.g., [ '42', 'rem' ]. + * A raw font size of `value + unit` is expected. If the value is an integer, it will convert to `value + 'px'`. + * + * @param {string|number} 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 ( typeof rawValue !== 'string' && typeof rawValue !== 'number' ) { + return null; + } + + // Converts numeric values to pixel values by default. + if ( isFinite( 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 55ef51a02aa4d..1702868f57681 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 0000000000000..cd45c3593e1a4 --- /dev/null +++ b/packages/block-editor/src/components/font-sizes/test/fluid-utils.js @@ -0,0 +1,168 @@ +/** + * WordPress dependencies + */ +import { logged } from '@wordpress/deprecated'; + +/** + * Internal dependencies + */ +import { + getComputedFluidTypographyValue, + getTypographyValueAndUnit, +} 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)' + ); + } ); + + describe( 'getTypographyValueAndUnit', () => { + it( 'should return the expected return values', () => { + [ + { + value: null, + expected: null, + }, + { + value: false, + expected: null, + }, + { + value: true, + expected: null, + }, + { + value: [ '10' ], + expected: null, + }, + { + value: '10vh', + expected: null, + }, + { + value: 'calc(2 * 10px)', + expected: null, + }, + { + value: 'clamp(15px, 0.9375rem + ((1vw - 7.68px) * 5.409), 60px)', + expected: null, + }, + { + value: '10', + expected: { + value: 10, + unit: 'px', + }, + }, + { + value: 11, + expected: { + value: 11, + unit: 'px', + }, + }, + { + value: 11.234, + expected: { + value: 11.234, + unit: 'px', + }, + }, + { + value: '12rem', + expected: { + value: 12, + unit: 'rem', + }, + }, + { + value: '12px', + expected: { + value: 12, + unit: 'px', + }, + }, + { + value: '12em', + expected: { + value: 12, + unit: 'em', + }, + }, + { + value: '12.74em', + expected: { + value: 12.74, + unit: 'em', + }, + }, + ].forEach( ( { value, expected } ) => { + expect( getTypographyValueAndUnit( value ) ).toEqual( + expected + ); + } ); + } ); + } ); +} ); diff --git a/packages/block-editor/src/hooks/font-size.js b/packages/block-editor/src/hooks/font-size.js index 40abdd8f8eb30..f8fa94538f53a 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/block-editor/src/hooks/test/use-typography-props.js b/packages/block-editor/src/hooks/test/use-typography-props.js index 93e4de29bc734..52f7bf97328ee 100644 --- a/packages/block-editor/src/hooks/test/use-typography-props.js +++ b/packages/block-editor/src/hooks/test/use-typography-props.js @@ -25,4 +25,26 @@ describe( 'getTypographyClassesAndStyles', () => { }, } ); } ); + + it( 'should return fluid font size styles', () => { + const attributes = { + fontFamily: 'tofu', + style: { + typography: { + letterSpacing: '22px', + fontSize: '2rem', + textTransform: 'uppercase', + }, + }, + }; + expect( getTypographyClassesAndStyles( attributes, true ) ).toEqual( { + className: 'has-tofu-font-family', + style: { + letterSpacing: '22px', + fontSize: + 'clamp(1.5rem, 1.5rem + ((1vw - 0.48rem) * 2.885), 3rem)', + textTransform: 'uppercase', + }, + } ); + } ); } ); diff --git a/packages/block-editor/src/hooks/use-typography-props.js b/packages/block-editor/src/hooks/use-typography-props.js index d08105d8d90c1..d70ae08aafc59 100644 --- a/packages/block-editor/src/hooks/use-typography-props.js +++ b/packages/block-editor/src/hooks/use-typography-props.js @@ -9,6 +9,7 @@ import classnames from 'classnames'; */ import { getInlineStyles } from './style'; import { getFontSizeClass } from '../components/font-sizes'; +import { getComputedFluidTypographyValue } from '../components/font-sizes/fluid-utils'; // This utility is intended to assist where the serialization of the typography // block support is being skipped for a block but the typography related CSS @@ -18,12 +19,26 @@ import { getFontSizeClass } from '../components/font-sizes'; * Provides the CSS class names and inline styles for a block's typography support * attributes. * - * @param {Object} attributes Block attributes. + * @param {Object} attributes Block attributes. + * @param {boolean} isFluidFontSizeActive Whether the function should try to convert font sizes to fluid values. * * @return {Object} Typography block support derived CSS classes & styles. */ -export function getTypographyClassesAndStyles( attributes ) { - const typographyStyles = attributes?.style?.typography || {}; +export function getTypographyClassesAndStyles( + attributes, + isFluidFontSizeActive +) { + let typographyStyles = attributes?.style?.typography || {}; + + if ( isFluidFontSizeActive ) { + typographyStyles = { + ...typographyStyles, + fontSize: getComputedFluidTypographyValue( { + fontSize: attributes?.style?.typography?.fontSize, + } ), + }; + } + const style = getInlineStyles( { typography: typographyStyles } ); const fontFamilyClassName = !! attributes?.fontFamily ? `has-${ kebabCase( attributes.fontFamily ) }-font-family` diff --git a/packages/block-library/src/navigation-link/index.php b/packages/block-library/src/navigation-link/index.php index 69d61f0e26a16..ba8f547b3221a 100644 --- a/packages/block-library/src/navigation-link/index.php +++ b/packages/block-library/src/navigation-link/index.php @@ -98,7 +98,14 @@ function block_core_navigation_link_build_css_font_sizes( $context ) { $font_sizes['css_classes'][] = sprintf( 'has-%s-font-size', $context['fontSize'] ); } elseif ( $has_custom_font_size ) { // Add the custom font size inline style. - $font_sizes['inline_styles'] = sprintf( 'font-size: %s;', $context['style']['typography']['fontSize'] ); + $font_sizes['inline_styles'] = sprintf( + 'font-size: %s;', + gutenberg_get_typography_font_size_value( + array( + 'size' => $context['style']['typography']['fontSize'], + ) + ) + ); } return $font_sizes; diff --git a/packages/block-library/src/navigation-submenu/index.php b/packages/block-library/src/navigation-submenu/index.php index 6ff4899575b27..40cfae52f1e9f 100644 --- a/packages/block-library/src/navigation-submenu/index.php +++ b/packages/block-library/src/navigation-submenu/index.php @@ -98,7 +98,14 @@ function block_core_navigation_submenu_build_css_font_sizes( $context ) { $font_sizes['css_classes'][] = sprintf( 'has-%s-font-size', $context['fontSize'] ); } elseif ( $has_custom_font_size ) { // Add the custom font size inline style. - $font_sizes['inline_styles'] = sprintf( 'font-size: %s;', $context['style']['typography']['fontSize'] ); + $font_sizes['inline_styles'] = sprintf( + 'font-size: %s;', + gutenberg_get_typography_font_size_value( + array( + 'size' => $context['style']['typography']['fontSize'], + ) + ) + ); } return $font_sizes; diff --git a/packages/block-library/src/page-list/index.php b/packages/block-library/src/page-list/index.php index 75a62bab2756c..9c7602fc4003e 100644 --- a/packages/block-library/src/page-list/index.php +++ b/packages/block-library/src/page-list/index.php @@ -119,7 +119,14 @@ function block_core_page_list_build_css_font_sizes( $context ) { $font_sizes['css_classes'][] = sprintf( 'has-%s-font-size', $context['fontSize'] ); } elseif ( $has_custom_font_size ) { // Add the custom font size inline style. - $font_sizes['inline_styles'] = sprintf( 'font-size: %s;', $context['style']['typography']['fontSize'] ); + $font_sizes['inline_styles'] = sprintf( + 'font-size: %s;', + gutenberg_get_typography_font_size_value( + array( + 'size' => $context['style']['typography']['fontSize'], + ) + ) + ); } return $font_sizes; diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js index 807dda04e743e..377788d281d3d 100644 --- a/packages/block-library/src/search/edit.js +++ b/packages/block-library/src/search/edit.js @@ -16,6 +16,7 @@ import { getTypographyClassesAndStyles as useTypographyProps, store as blockEditorStore, __experimentalGetElementClassName, + useSetting, } from '@wordpress/block-editor'; import { useDispatch, useSelect } from '@wordpress/data'; import { useEffect } from '@wordpress/element'; @@ -113,7 +114,11 @@ export default function SearchEdit( { } const colorProps = useColorProps( attributes ); - const typographyProps = useTypographyProps( attributes ); + const fluidTypographyEnabled = useSetting( 'typography.fluid' ); + const typographyProps = useTypographyProps( + attributes, + fluidTypographyEnabled + ); const unitControlInstanceId = useInstanceId( UnitControl ); const unitControlInputId = `wp-block-search__width-${ unitControlInstanceId }`; const isButtonPositionInside = 'button-inside' === buttonPosition; diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php index 46e08134b384d..851131a5fb5d7 100644 --- a/packages/block-library/src/search/index.php +++ b/packages/block-library/src/search/index.php @@ -381,7 +381,7 @@ function styles_for_block_core_search( $attributes ) { } // Get typography styles to be shared across inner elements. - $typography_styles = get_typography_styles_for_block_core_search( $attributes ); + $typography_styles = esc_attr( get_typography_styles_for_block_core_search( $attributes ) ); if ( ! empty( $typography_styles ) ) { $label_styles [] = $typography_styles; $button_styles[] = $typography_styles; @@ -442,31 +442,39 @@ function get_typography_styles_for_block_core_search( $attributes ) { // Add typography styles. if ( ! empty( $attributes['style']['typography']['fontSize'] ) ) { - $typography_styles[] = sprintf( 'font-size: %s;', esc_attr( $attributes['style']['typography']['fontSize'] ) ); + $typography_styles[] = sprintf( + 'font-size: %s;', + gutenberg_get_typography_font_size_value( + array( + 'size' => $attributes['style']['typography']['fontSize'], + ) + ) + ); + } if ( ! empty( $attributes['style']['typography']['fontFamily'] ) ) { - $typography_styles[] = sprintf( 'font-family: %s;', esc_attr( $attributes['style']['typography']['fontFamily'] ) ); + $typography_styles[] = sprintf( 'font-family: %s;', $attributes['style']['typography']['fontFamily'] ); } if ( ! empty( $attributes['style']['typography']['letterSpacing'] ) ) { - $typography_styles[] = sprintf( 'letter-spacing: %s;', esc_attr( $attributes['style']['typography']['letterSpacing'] ) ); + $typography_styles[] = sprintf( 'letter-spacing: %s;', $attributes['style']['typography']['letterSpacing'] ); } if ( ! empty( $attributes['style']['typography']['fontWeight'] ) ) { - $typography_styles[] = sprintf( 'font-weight: %s;', esc_attr( $attributes['style']['typography']['fontWeight'] ) ); + $typography_styles[] = sprintf( 'font-weight: %s;', $attributes['style']['typography']['fontWeight'] ); } if ( ! empty( $attributes['style']['typography']['fontStyle'] ) ) { - $typography_styles[] = sprintf( 'font-style: %s;', esc_attr( $attributes['style']['typography']['fontStyle'] ) ); + $typography_styles[] = sprintf( 'font-style: %s;', $attributes['style']['typography']['fontStyle'] ); } if ( ! empty( $attributes['style']['typography']['lineHeight'] ) ) { - $typography_styles[] = sprintf( 'line-height: %s;', esc_attr( $attributes['style']['typography']['lineHeight'] ) ); + $typography_styles[] = sprintf( 'line-height: %s;', $attributes['style']['typography']['lineHeight'] ); } if ( ! empty( $attributes['style']['typography']['textTransform'] ) ) { - $typography_styles[] = sprintf( 'text-transform: %s;', esc_attr( $attributes['style']['typography']['textTransform'] ) ); + $typography_styles[] = sprintf( 'text-transform: %s;', $attributes['style']['typography']['textTransform'] ); } return implode( '', $typography_styles ); diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 7cece0b1abf53..31ad5b0457d7e 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -4,6 +4,7 @@ ### Bug Fix +- `FontSizePicker`: Ensure that fluid font size presets appear correctly in the UI controls ([#44791](https://github.com/WordPress/gutenberg/pull/44791)) - The `LinkedButton` to unlink sides in `BoxControl`, `BorderBoxControl` and `BorderRadiusControl` have changed from a rectangular primary button to an icon-only button, with a sentence case tooltip, and default-size icon for better legibility. The `Button` component has been fixed so when `isSmall` and `icon` props are set, and no text is present, the button shape is square rather than rectangular. - `Popover`: fix limitShift logic by adding iframe offset correctly [#42950](https://github.com/WordPress/gutenberg/pull/42950)). diff --git a/packages/components/src/font-size-picker/test/utils.js b/packages/components/src/font-size-picker/test/utils.js index fc074514925cc..a640aed862387 100644 --- a/packages/components/src/font-size-picker/test/utils.js +++ b/packages/components/src/font-size-picker/test/utils.js @@ -116,33 +116,33 @@ describe( 'getToggleGroupOptions', () => { ).toEqual( [ { key: '1', - value: '1', label: 'S', name: '1', + value: '1', }, { key: '2', - value: '2', label: 'M', name: '2', + value: '2', }, { key: '3', - value: '3', label: 'L', name: '3', + value: '3', }, { key: '4', - value: '4', label: 'XL', name: '4', + value: '4', }, { key: '5', - value: '5', label: 'XXL', name: 'XXL', + value: '5', }, ] ); } ); diff --git a/packages/components/src/font-size-picker/utils.js b/packages/components/src/font-size-picker/utils.js index fff3d087cf3e6..dbb7882183fab 100644 --- a/packages/components/src/font-size-picker/utils.js +++ b/packages/components/src/font-size-picker/utils.js @@ -91,7 +91,7 @@ function getSelectOptions( optionsArray, disableCustomFontSizes ) { ]; return options.map( ( { slug, name, size } ) => ( { key: slug, - name, + name: name || slug, size, __experimentalHint: size && isSimpleCssValue( size ) && parseFloat( size ), 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 cbc0fe53a3390..97ebeb583e497 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,31 @@ describe( 'typography utils', () => { typographySettings: undefined, expected: '28px', }, + // Default return value where font size is 0. + { + preset: { + size: 0, + }, + typographySettings: undefined, + expected: 0, + }, + // Default return value where font size is '0'. + { + preset: { + size: '0', + }, + typographySettings: undefined, + expected: '0', + }, + + // Default return non-fluid value where `size` is undefined. + { + preset: { + size: undefined, + }, + typographySettings: undefined, + expected: undefined, + }, // Should return non-fluid value when fluid is `false`. { preset: { @@ -26,6 +51,54 @@ describe( 'typography utils', () => { }, expected: '28px', }, + // Should coerce integer to `px` and return fluid value. + { + preset: { + size: 33, + fluid: true, + }, + typographySettings: { + fluid: true, + }, + expected: + 'clamp(24.75px, 1.546875rem + ((1vw - 7.68px) * 2.975), 49.5px)', + }, + + // Should coerce float to `px` and return fluid value. + { + preset: { + size: 100.23, + fluid: true, + }, + typographySettings: { + fluid: true, + }, + expected: + 'clamp(75.1725px, 4.69828125rem + ((1vw - 7.68px) * 9.035), 150.345px)', + }, + // Should return incoming value when already clamped. + { + preset: { + size: 'clamp(21px, 1.3125rem + ((1vw - 7.68px) * 2.524), 42px)', + fluid: false, + }, + typographySettings: { + fluid: true, + }, + expected: + 'clamp(21px, 1.3125rem + ((1vw - 7.68px) * 2.524), 42px)', + }, + // Should return incoming value with unsupported unit. + { + preset: { + size: '1000%', + fluid: false, + }, + typographySettings: { + fluid: true, + }, + expected: '1000%', + }, // Should return fluid value. { preset: { @@ -37,6 +110,17 @@ describe( 'typography utils', () => { expected: 'clamp(1.3125rem, 1.3125rem + ((1vw - 0.48rem) * 2.524), 2.625rem)', }, + // Should return fluid value for floats with units. + { + preset: { + size: '100.175px', + }, + typographySettings: { + fluid: true, + }, + expected: + 'clamp(75.13125px, 4.695703125rem + ((1vw - 7.68px) * 9.03), 150.2625px)', + }, // Should return default fluid values with empty fluid array. { preset: { diff --git a/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js b/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js index 8e8686ca4eed8..48f21603ad78e 100644 --- a/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js +++ b/packages/edit-site/src/components/global-styles/test/use-global-styles-output.js @@ -711,10 +711,11 @@ describe( 'global styles renderer', () => { }, typography: { fontFamily: 'sans-serif', + fontSize: '14px', }, }; - it( 'Should output padding variables and other properties if useRootPaddingAwareAlignments is enabled', () => { + it( 'should output padding variables and other properties if useRootPaddingAwareAlignments is enabled', () => { expect( getStylesDeclarations( blockStyles, 'body', true ) ).toEqual( [ @@ -724,10 +725,11 @@ describe( 'global styles renderer', () => { '--wp--style--root--padding-left: 33px', 'background-color: var(--wp--preset--color--light-green-cyan)', 'font-family: sans-serif', + 'font-size: 14px', ] ); } ); - it( 'Should output padding and other properties if useRootPaddingAwareAlignments is disabled', () => { + it( 'should output padding and other properties if useRootPaddingAwareAlignments is disabled', () => { expect( getStylesDeclarations( blockStyles, 'body', false ) ).toEqual( [ @@ -737,10 +739,11 @@ describe( 'global styles renderer', () => { 'padding-bottom: 33px', 'padding-left: 33px', 'font-family: sans-serif', + 'font-size: 14px', ] ); } ); - it( 'Should not output padding variables if selector is not root', () => { + it( 'should not output padding variables if selector is not root', () => { expect( getStylesDeclarations( blockStyles, @@ -754,6 +757,57 @@ describe( 'global styles renderer', () => { 'padding-bottom: 33px', 'padding-left: 33px', 'font-family: sans-serif', + 'font-size: 14px', + ] ); + } ); + + it( 'should output clamp values for font-size when fluid typography is enabled', () => { + expect( + getStylesDeclarations( + blockStyles, + '.wp-block-site-title', + true, + { + settings: { + typography: { + fluid: true, + }, + }, + } + ) + ).toEqual( [ + 'background-color: var(--wp--preset--color--light-green-cyan)', + 'padding-top: 33px', + 'padding-right: 33px', + 'padding-bottom: 33px', + 'padding-left: 33px', + 'font-family: sans-serif', + 'font-size: clamp(10.5px, 0.65625rem + ((1vw - 7.68px) * 1.262), 21px)', + ] ); + } ); + + it( 'should output direct values for font-size when fluid typography is disabled', () => { + expect( + getStylesDeclarations( + blockStyles, + '.wp-block-site-title', + true, + { + settings: { + typography: { + fluid: false, + }, + }, + } + ) + ).toEqual( [ + 'background-color: var(--wp--preset--color--light-green-cyan)', + 'padding-top: 33px', + 'padding-right: 33px', + 'padding-bottom: 33px', + 'padding-left: 33px', + 'font-family: sans-serif', + 'font-size: 14px', ] ); } ); } ); diff --git a/packages/edit-site/src/components/global-styles/typography-panel.js b/packages/edit-site/src/components/global-styles/typography-panel.js index f4c77abf0fd71..86a929fe4998b 100644 --- a/packages/edit-site/src/components/global-styles/typography-panel.js +++ b/packages/edit-site/src/components/global-styles/typography-panel.js @@ -21,6 +21,7 @@ import { useState } from '@wordpress/element'; * Internal dependencies */ import { getSupportedGlobalStylesPanels, useSetting, useStyle } from './hooks'; +import { getTypographyFontSizeValue } from './typography-utils'; export function useHasTypographyPanel( name ) { const hasLineHeight = useHasLineHeightControl( name ); @@ -87,7 +88,19 @@ export default function TypographyPanel( { name, element } ) { } else if ( element && element !== 'text' ) { prefix = `elements.${ element }.`; } + const [ fluidTypography ] = useSetting( 'typography.fluid', name ); const [ fontSizes ] = useSetting( 'typography.fontSizes', name ); + + // Convert static font size values to fluid font sizes if fluidTypography is activated. + const fontSizesWithFluidValues = fontSizes.map( ( font ) => { + if ( !! fluidTypography ) { + font.size = getTypographyFontSizeValue( font, { + fluid: fluidTypography, + } ); + } + return font; + } ); + const disableCustomFontSizes = ! useSetting( 'typography.customFontSize', name @@ -180,7 +193,7 @@ export default function TypographyPanel( { name, element } ) { > @@ -227,7 +240,7 @@ export default function TypographyPanel( { name, element } ) { test_block_name = null; + + // Sets up the `wp-content/themes/` directory to ensure consistency when running tests. + $this->theme_root = realpath( __DIR__ . '/../data/themedir1' ); + $this->orig_theme_dir = $GLOBALS['wp_theme_directories']; + $GLOBALS['wp_theme_directories'] = array( WP_CONTENT_DIR . '/themes', $this->theme_root ); + + $theme_root_callback = function () { + return $this->theme_root; + }; + add_filter( 'theme_root', $theme_root_callback ); + add_filter( 'stylesheet_root', $theme_root_callback ); + add_filter( 'template_root', $theme_root_callback ); + + // Clear caches. + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); } /** * Tears down tests. */ - function tear_down() { + public function tear_down() { + // Restores the original theme directory setup. + $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir; + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + + // Resets test block name. unregister_block_type( $this->test_block_name ); $this->test_block_name = null; + parent::tear_down(); } @@ -34,7 +73,7 @@ function tear_down() { * * @covers ::wp_apply_typography_support */ - function test_should_kebab_case_font_size_slug_with_numbers() { + public function test_should_kebab_case_font_size_slug_with_numbers() { $this->test_block_name = 'test/font-size-slug-with-numbers'; register_block_type( $this->test_block_name, @@ -68,7 +107,7 @@ function test_should_kebab_case_font_size_slug_with_numbers() { * * @covers ::wp_apply_typography_support */ - function test_should_generate_font_family_with_legacy_inline_styles_using_a_value() { + public function test_should_generate_font_family_with_legacy_inline_styles_using_a_value() { $this->test_block_name = 'test/font-family-with-inline-styles-using-value'; register_block_type( $this->test_block_name, @@ -101,7 +140,7 @@ function test_should_generate_font_family_with_legacy_inline_styles_using_a_valu * * @covers ::wp_apply_typography_support */ - function test_should_skip_serialization_for_typography_block_supports() { + public function test_should_skip_serialization_for_typography_block_supports() { $this->test_block_name = 'test/typography-with-skipped-serialization-block-supports'; register_block_type( $this->test_block_name, @@ -147,7 +186,7 @@ function test_should_skip_serialization_for_typography_block_supports() { * * @covers ::wp_apply_typography_support */ - function test_should_skip_serialization_for_letter_spacing_block_supports() { + public function test_should_skip_serialization_for_letter_spacing_block_supports() { $this->test_block_name = 'test/letter-spacing-with-individual-skipped-serialization-block-supports'; register_block_type( $this->test_block_name, @@ -183,7 +222,7 @@ function test_should_skip_serialization_for_letter_spacing_block_supports() { * * @covers ::wp_apply_typography_support */ - function test_should_generate_css_var_for_font_family_with_legacy_inline_styles() { + public function test_should_generate_css_var_for_font_family_with_legacy_inline_styles() { $this->test_block_name = 'test/font-family-with-inline-styles-using-css-var'; register_block_type( $this->test_block_name, @@ -216,7 +255,7 @@ function test_should_generate_css_var_for_font_family_with_legacy_inline_styles( * * @covers ::wp_apply_typography_support */ - function test_should_generate_classname_for_font_family() { + public function test_should_generate_classname_for_font_family() { $this->test_block_name = 'test/font-family-with-class'; register_block_type( $this->test_block_name, @@ -263,7 +302,7 @@ function test_should_generate_classname_for_font_family() { * @param bool $should_use_fluid_typography An override to switch fluid typography "on". Can be used for unit testing. * @param string $expected_output Expected output of gutenberg_get_typography_font_size_value(). */ - function test_gutenberg_get_typography_font_size_value( $font_size_preset, $should_use_fluid_typography, $expected_output ) { + public function test_gutenberg_get_typography_font_size_value( $font_size_preset, $should_use_fluid_typography, $expected_output ) { $actual = gutenberg_get_typography_font_size_value( $font_size_preset, $should_use_fluid_typography ); $this->assertSame( $expected_output, $actual ); @@ -284,6 +323,30 @@ public function data_generate_font_size_preset_fixtures() { 'expected_output' => '28px', ), + 'size: int 0' => array( + 'font_size_preset' => array( + 'size' => 0, + ), + 'should_use_fluid_typography' => true, + 'expected_output' => 0, + ), + + 'size: string 0' => array( + 'font_size_preset' => array( + 'size' => '0', + ), + 'should_use_fluid_typography' => true, + 'expected_output' => '0', + ), + + 'default_return_value_when_size_is_undefined' => array( + 'font_size_preset' => array( + 'size' => null, + ), + 'should_use_fluid_typography' => false, + 'expected_output' => null, + ), + 'default_return_value_when_fluid_is_false' => array( 'font_size_preset' => array( 'size' => '28px', @@ -293,6 +356,24 @@ public function data_generate_font_size_preset_fixtures() { 'expected_output' => '28px', ), + 'default_return_value_when_value_is_already_clamped' => array( + 'font_size_preset' => array( + 'size' => 'clamp(21px, 1.3125rem + ((1vw - 7.68px) * 2.524), 42px)', + 'fluid' => false, + ), + 'should_use_fluid_typography' => true, + 'expected_output' => 'clamp(21px, 1.3125rem + ((1vw - 7.68px) * 2.524), 42px)', + ), + + 'default_return_value_with_unsupported_unit' => array( + 'font_size_preset' => array( + 'size' => '1000%', + 'fluid' => false, + ), + 'should_use_fluid_typography' => true, + 'expected_output' => '1000%', + ), + 'return_fluid_value' => array( 'font_size_preset' => array( 'size' => '1.75rem', @@ -301,6 +382,30 @@ public function data_generate_font_size_preset_fixtures() { 'expected_output' => 'clamp(1.3125rem, 1.3125rem + ((1vw - 0.48rem) * 2.524), 2.625rem)', ), + 'return_fluid_value_with_floats_with_units' => array( + 'font_size_preset' => array( + 'size' => '100.175px', + ), + 'should_use_fluid_typography' => true, + 'expected_output' => 'clamp(75.13125px, 4.695703125rem + ((1vw - 7.68px) * 9.03), 150.2625px)', + ), + + 'return_fluid_value_with_integer_coerced_to_px' => array( + 'font_size_preset' => array( + 'size' => 33, + ), + 'should_use_fluid_typography' => true, + 'expected_output' => 'clamp(24.75px, 1.546875rem + ((1vw - 7.68px) * 2.975), 49.5px)', + ), + + 'return_fluid_value_with_float_coerced_to_px' => array( + 'font_size_preset' => array( + 'size' => 100.23, + ), + 'should_use_fluid_typography' => true, + 'expected_output' => 'clamp(75.1725px, 4.69828125rem + ((1vw - 7.68px) * 9.035), 150.345px)', + ), + 'return_default_fluid_values_with_empty_fluid_array' => array( 'font_size_preset' => array( 'size' => '28px', @@ -366,4 +471,276 @@ public function data_generate_font_size_preset_fixtures() { ), ); } + + /** + * Tests that custom font sizes are converted to fluid values + * in inline block supports styles, + * when "settings.typography.fluid" is set to `true`. + * + * @covers ::gutenberg_register_typography_support + * + * @dataProvider data_generate_block_supports_font_size_fixtures + * + * @param string $font_size_value The block supports custom font size value. + * @param bool $should_use_fluid_typography An override to switch fluid typography "on". Can be used for unit testing. + * @param string $expected_output Expected value of style property from gutenberg_apply_typography_support(). + */ + public function test_should_covert_font_sizes_to_fluid_values( $font_size_value, $should_use_fluid_typography, $expected_output ) { + if ( $should_use_fluid_typography ) { + switch_theme( 'block-theme-child-with-fluid-typography' ); + } else { + switch_theme( 'default' ); + } + + $this->test_block_name = 'test/font-size-fluid-value'; + register_block_type( + $this->test_block_name, + array( + 'api_version' => 2, + 'attributes' => array( + 'style' => array( + 'type' => 'object', + ), + ), + 'supports' => array( + 'typography' => array( + 'fontSize' => true, + ), + ), + ) + ); + $registry = WP_Block_Type_Registry::get_instance(); + $block_type = $registry->get_registered( $this->test_block_name ); + $block_attributes = array( + 'style' => array( + 'typography' => array( + 'fontSize' => $font_size_value, + ), + ), + ); + + $actual = gutenberg_apply_typography_support( $block_type, $block_attributes ); + $expected = array( 'style' => $expected_output ); + + $this->assertSame( $expected, $actual ); + } + + /** + * Data provider for test_should_covert_font_sizes_to_fluid_values. + * + * @return array + */ + public function data_generate_block_supports_font_size_fixtures() { + return array( + 'default_return_value' => array( + 'font_size_value' => '50px', + 'should_use_fluid_typography' => false, + 'expected_output' => 'font-size:50px;', + ), + 'return_value_with_fluid_typography' => array( + 'font_size_value' => '50px', + 'should_use_fluid_typography' => true, + 'expected_output' => 'font-size:clamp(37.5px, 2.34375rem + ((1vw - 7.68px) * 4.507), 75px);', + ), + ); + } + + /** + * Tests that a block element's custom font size in the inline style attribute + * is replaced with a fluid value when "settings.typography.fluid" is set to `true`, + * and the correct block content is generated. + * + * @covers ::gutenberg_render_typography_support + * + * @dataProvider data_generate_replace_inline_font_styles_with_fluid_values_fixtures + * + * @param string $block_content HTML block content. + * @param string $font_size_value The block supports custom font size value. + * @param bool $should_use_fluid_typography An override to switch fluid typography "on". Can be used for unit testing. + * @param string $expected_output Expected value of style property from gutenberg_apply_typography_support(). + */ + public function test_should_replace_inline_font_styles_with_fluid_values( $block_content, $font_size_value, $should_use_fluid_typography, $expected_output ) { + if ( $should_use_fluid_typography ) { + switch_theme( 'block-theme-child-with-fluid-typography' ); + } else { + switch_theme( 'default' ); + } + + $block = array( + 'blockName' => 'core/image', + 'attrs' => array( + 'style' => array( + 'typography' => array( + 'fontSize' => $font_size_value, + ), + ), + ), + ); + $actual = gutenberg_render_typography_support( $block_content, $block ); + + $this->assertSame( $expected_output, $actual ); + } + + /** + * Data provider for test_should_replace_inline_font_styles_with_fluid_values. + * + * @return array + */ + public function data_generate_replace_inline_font_styles_with_fluid_values_fixtures() { + return array( + 'default_return_content' => array( + 'block_content' => '', + 'font_size_value' => '4rem', + 'should_use_fluid_typography' => false, + 'expected_output' => '', + ), + 'return_content_with_replaced_fluid_font_size_inline_style' => array( + 'block_content' => '', + 'font_size_value' => '4rem', + 'should_use_fluid_typography' => true, + 'expected_output' => '', + ), + 'return_content_if_no_inline_font_size_found' => array( + 'block_content' => '

A paragraph inside a group

', + 'font_size_value' => '20px', + 'should_use_fluid_typography' => true, + 'expected_output' => '

A paragraph inside a group

', + ), + 'return_content_css_var' => array( + 'block_content' => '

A paragraph inside a group

', + 'font_size_value' => 'var:preset|font-size|x-large', + 'should_use_fluid_typography' => true, + 'expected_output' => '

A paragraph inside a group

', + ), + 'return_content_with_spaces' => array( + 'block_content' => '

A paragraph inside a group

', + 'font_size_value' => '20px', + 'should_use_fluid_typography' => true, + 'expected_output' => '

A paragraph inside a group

', + ), + 'return_content_with_first_match_replace_only' => array( + 'block_content' => "
\n \n

A paragraph inside a group

", + 'font_size_value' => '1em', + 'should_use_fluid_typography' => true, + 'expected_output' => "
\n \n

A paragraph inside a group

", + ), + ); + } + + /** + * Tests that valid font size values are parsed. + * + * @ticket 56467 + * + * @covers ::gutenberg_get_typography_value_and_unit + * + * @dataProvider data_valid_size_wp_get_typography_value_and_unit + * + * @param mixed $raw_value Raw size value to test. + * @param mixed $expected An expected return value. + */ + public function test_valid_size_wp_get_typography_value_and_unit( $raw_value, $expected ) { + $this->assertEquals( $expected, gutenberg_get_typography_value_and_unit( $raw_value ) ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_valid_size_wp_get_typography_value_and_unit() { + return array( + 'size: 10vh with default units do not match' => array( + 'raw_value' => '10vh', + 'expected' => null, + ), + 'size: calc() values do not match' => array( + 'raw_value' => 'calc(2 * 10px)', + 'expected' => null, + ), + 'size: clamp() values do not match' => array( + 'raw_value' => 'clamp(15px, 0.9375rem + ((1vw - 7.68px) * 5.409), 60px)', + 'expected' => null, + ), + 'size: `"10"`' => array( + 'raw_value' => '10', + 'expected' => array( + 'value' => 10, + 'unit' => 'px', + ), + ), + 'size: `11`' => array( + 'raw_value' => 11, + 'expected' => array( + 'value' => 11, + 'unit' => 'px', + ), + ), + 'size: `11.234`' => array( + 'raw_value' => '11.234', + 'expected' => array( + 'value' => 11.234, + 'unit' => 'px', + ), + ), + 'size: `"12rem"`' => array( + 'raw_value' => '12rem', + 'expected' => array( + 'value' => 12, + 'unit' => 'rem', + ), + ), + 'size: `"12px"`' => array( + 'raw_value' => '12px', + 'expected' => array( + 'value' => 12, + 'unit' => 'px', + ), + ), + 'size: `"12em"`' => array( + 'raw_value' => '12em', + 'expected' => array( + 'value' => 12, + 'unit' => 'em', + ), + ), + 'size: `"12.74em"`' => array( + 'raw_value' => '12.74em', + 'expected' => array( + 'value' => 12.74, + 'unit' => 'em', + ), + ), + ); + } + + /** + * Tests that invalid font size values are not parsed and trigger incorrect usage. + * + * @ticket 56467 + * + * @covers ::gutenberg_get_typography_value_and_unit + * + * @dataProvider data_invalid_size_wp_get_typography_value_and_unit + * @expectedIncorrectUsage gutenberg_get_typography_value_and_unit + * + * @param mixed $raw_value Raw size value to test. + */ + public function test_invalid_size_wp_get_typography_value_and_unit( $raw_value ) { + $this->assertNull( gutenberg_get_typography_value_and_unit( $raw_value ) ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_invalid_size_wp_get_typography_value_and_unit() { + return array( + 'size: null' => array( null ), + 'size: false' => array( false ), + 'size: true' => array( true ), + 'size: array' => array( array( '10' ) ), + ); + } } diff --git a/phpunit/data/themedir1/block-theme-child-with-fluid-typography/theme.json b/phpunit/data/themedir1/block-theme-child-with-fluid-typography/theme.json new file mode 100644 index 0000000000000..93234766eddd2 --- /dev/null +++ b/phpunit/data/themedir1/block-theme-child-with-fluid-typography/theme.json @@ -0,0 +1,9 @@ +{ + "version": 2, + "settings": { + "appearanceTools": true, + "typography": { + "fluid": true + } + } +}