Skip to content

Commit d082b28

Browse files
committed
Block Supports: Add text indent to typography supports
This commit adds CSS text-indent support for paragraph blocks with traditional typography conventions - subsequent paragraphs get first-line indented in LTR languages, with an option to indent all paragraphs (default for RTL languages). Text indentation is a fundamental typography feature. Traditional print typography conventions dictate that only subsequent paragraphs (not the first) should have their first line indented in LTR languages, while RTL languages typically indent all paragraphs. This feature enables proper typographic styling that matches publishing standards. Props aaronrobertshaw, ramonopoly, andrewserong, wildworks, matveb, skorasaurus, greenshady, kjellr. Fixes #64326. git-svn-id: https://develop.svn.wordpress.org/trunk@61632 602fd350-edb4-49c9-b593-d223f7449a82
1 parent d0a9538 commit d082b28

4 files changed

Lines changed: 73 additions & 1 deletion

File tree

src/wp-includes/block-supports/typography.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
*
1212
* @since 5.6.0
1313
* @since 6.3.0 Added support for text-columns.
14+
* @since 7.0.0 Added support for text-indent.
1415
* @access private
1516
*
1617
* @param WP_Block_Type $block_type Block Type.
@@ -35,6 +36,7 @@ function wp_register_typography_support( $block_type ) {
3536
$has_text_columns_support = $typography_supports['textColumns'] ?? false;
3637
$has_text_decoration_support = $typography_supports['__experimentalTextDecoration'] ?? false;
3738
$has_text_transform_support = $typography_supports['__experimentalTextTransform'] ?? false;
39+
$has_text_indent_support = $typography_supports['textIndent'] ?? false;
3840
$has_writing_mode_support = $typography_supports['__experimentalWritingMode'] ?? false;
3941

4042
$has_typography_support = $has_font_family_support
@@ -47,6 +49,7 @@ function wp_register_typography_support( $block_type ) {
4749
|| $has_text_columns_support
4850
|| $has_text_decoration_support
4951
|| $has_text_transform_support
52+
|| $has_text_indent_support
5053
|| $has_writing_mode_support;
5154

5255
if ( ! $block_type->attributes ) {
@@ -80,6 +83,7 @@ function wp_register_typography_support( $block_type ) {
8083
* @since 5.6.0
8184
* @since 6.1.0 Used the style engine to generate CSS and classnames.
8285
* @since 6.3.0 Added support for text-columns.
86+
* @since 7.0.0 Added support for text-indent.
8387
* @access private
8488
*
8589
* @param WP_Block_Type $block_type Block type.
@@ -110,6 +114,7 @@ function wp_apply_typography_support( $block_type, $block_attributes ) {
110114
$has_text_columns_support = $typography_supports['textColumns'] ?? false;
111115
$has_text_decoration_support = $typography_supports['__experimentalTextDecoration'] ?? false;
112116
$has_text_transform_support = $typography_supports['__experimentalTextTransform'] ?? false;
117+
$has_text_indent_support = $typography_supports['textIndent'] ?? false;
113118
$has_writing_mode_support = $typography_supports['__experimentalWritingMode'] ?? false;
114119

115120
// Whether to skip individual block support features.
@@ -123,6 +128,7 @@ function wp_apply_typography_support( $block_type, $block_attributes ) {
123128
$should_skip_text_decoration = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textDecoration' );
124129
$should_skip_text_transform = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textTransform' );
125130
$should_skip_letter_spacing = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'letterSpacing' );
131+
$should_skip_text_indent = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textIndent' );
126132
$should_skip_writing_mode = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'writingMode' );
127133

128134
$typography_block_styles = array();
@@ -222,6 +228,10 @@ function wp_apply_typography_support( $block_type, $block_attributes ) {
222228
$typography_block_styles['writingMode'] = $block_attributes['style']['typography']['writingMode'] ?? null;
223229
}
224230

231+
if ( $has_text_indent_support && ! $should_skip_text_indent && isset( $block_attributes['style']['typography']['textIndent'] ) ) {
232+
$typography_block_styles['textIndent'] = $block_attributes['style']['typography']['textIndent'] ?? null;
233+
}
234+
225235
$attributes = array();
226236
$classnames = array();
227237
$styles = wp_style_engine_get_styles(

src/wp-includes/class-wp-theme-json.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ class WP_Theme_JSON {
247247
* @since 6.6.0 Added `background-[image|position|repeat|size]` properties.
248248
* @since 6.7.0 Added `background-attachment` property.
249249
* @since 7.0.0 Added `dimensions.width` and `dimensions.height`.
250+
* Added `text-indent` property.
250251
* @var array
251252
*/
252253
const PROPERTIES_METADATA = array(
@@ -309,6 +310,7 @@ class WP_Theme_JSON {
309310
'--wp--style--root--padding-left' => array( 'spacing', 'padding', 'left' ),
310311
'text-decoration' => array( 'typography', 'textDecoration' ),
311312
'text-transform' => array( 'typography', 'textTransform' ),
313+
'text-indent' => array( 'typography', 'textIndent' ),
312314
'filter' => array( 'filter', 'duotone' ),
313315
'box-shadow' => array( 'shadow' ),
314316
'height' => array( 'dimensions', 'height' ),
@@ -409,6 +411,7 @@ class WP_Theme_JSON {
409411
* @since 6.9.0 Added support for `border.radiusSizes`.
410412
* @since 7.0.0 Added type markers to the schema for boolean values.
411413
* Added support for `dimensions.width` and `dimensions.height`.
414+
* Added support for `typography.textIndent`.
412415
* @var array
413416
*/
414417
const VALID_SETTINGS = array(
@@ -494,6 +497,7 @@ class WP_Theme_JSON {
494497
'textAlign' => null,
495498
'textColumns' => null,
496499
'textDecoration' => null,
500+
'textIndent' => null,
497501
'textTransform' => null,
498502
'writingMode' => null,
499503
),
@@ -601,6 +605,7 @@ class WP_Theme_JSON {
601605
'textAlign' => null,
602606
'textColumns' => null,
603607
'textDecoration' => null,
608+
'textIndent' => null,
604609
'textTransform' => null,
605610
'writingMode' => null,
606611
),
@@ -2749,6 +2754,48 @@ private static function update_separator_declarations( $declarations ) {
27492754
return $declarations;
27502755
}
27512756

2757+
/**
2758+
* Updates the text indent selector for paragraph blocks based on the textIndent setting.
2759+
*
2760+
* The textIndent setting can be 'subsequent' (default), 'all', or false.
2761+
* When set to 'all', the selector should be '.wp-block-paragraph' instead of
2762+
* '.wp-block-paragraph + .wp-block-paragraph' to apply indent to all paragraphs.
2763+
*
2764+
* @since 7.0.0
2765+
*
2766+
* @param array $feature_declarations The feature declarations keyed by selector.
2767+
* @param array $settings The theme.json settings.
2768+
* @param string $block_name The block name being processed.
2769+
* @return array The updated feature declarations.
2770+
*/
2771+
private static function update_paragraph_text_indent_selector( $feature_declarations, $settings, $block_name ) {
2772+
if ( 'core/paragraph' !== $block_name ) {
2773+
return $feature_declarations;
2774+
}
2775+
2776+
// Check block-level settings first, then fall back to global settings.
2777+
$block_settings = $settings['blocks']['core/paragraph'] ?? null;
2778+
$text_indent_setting = $block_settings['typography']['textIndent']
2779+
?? $settings['typography']['textIndent']
2780+
?? 'subsequent';
2781+
2782+
if ( 'all' !== $text_indent_setting ) {
2783+
return $feature_declarations;
2784+
}
2785+
2786+
// Look for the text indent selector and replace it.
2787+
$old_selector = '.wp-block-paragraph + .wp-block-paragraph';
2788+
$new_selector = '.wp-block-paragraph';
2789+
2790+
if ( isset( $feature_declarations[ $old_selector ] ) ) {
2791+
$declarations = $feature_declarations[ $old_selector ];
2792+
unset( $feature_declarations[ $old_selector ] );
2793+
$feature_declarations[ $new_selector ] = $declarations;
2794+
}
2795+
2796+
return $feature_declarations;
2797+
}
2798+
27522799
/**
27532800
* An internal method to get the block nodes from a theme.json file.
27542801
*
@@ -2910,6 +2957,10 @@ public function get_styles_for_block( $block_metadata ) {
29102957
$feature_declarations = static::get_feature_declarations_for_node( $block_metadata, $node );
29112958
$is_root_selector = static::ROOT_BLOCK_SELECTOR === $selector;
29122959

2960+
// Update text indent selector for paragraph blocks based on the textIndent setting.
2961+
$block_name = $block_metadata['name'] ?? null;
2962+
$feature_declarations = static::update_paragraph_text_indent_selector( $feature_declarations, $settings, $block_name );
2963+
29132964
// If there are style variations, generate the declarations for them, including any feature selectors the block may have.
29142965
$style_variation_declarations = array();
29152966
$style_variation_custom_css = array();
@@ -2922,6 +2973,9 @@ public function get_styles_for_block( $block_metadata ) {
29222973
// Generate any feature/subfeature style declarations for the current style variation.
29232974
$variation_declarations = static::get_feature_declarations_for_node( $block_metadata, $style_variation_node );
29242975

2976+
// Update text indent selector for paragraph blocks based on the textIndent setting.
2977+
$variation_declarations = static::update_paragraph_text_indent_selector( $variation_declarations, $settings, $block_name );
2978+
29252979
// Combine selectors with style variation's selector and add to overall style variation declarations.
29262980
foreach ( $variation_declarations as $current_selector => $new_declarations ) {
29272981
/*

src/wp-includes/style-engine/class-wp-style-engine.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
* @since 6.5.0 Added support for background.backgroundPosition,
2727
* background.backgroundRepeat and dimensions.aspectRatio.
2828
* @since 6.7.0 Added support for typography.writingMode.
29+
* @since 7.0.0 Added support for typography.textIndent.
2930
*/
3031
#[AllowDynamicProperties]
3132
final class WP_Style_Engine {
@@ -315,6 +316,12 @@ final class WP_Style_Engine {
315316
),
316317
'path' => array( 'typography', 'textDecoration' ),
317318
),
319+
'textIndent' => array(
320+
'property_keys' => array(
321+
'default' => 'text-indent',
322+
),
323+
'path' => array( 'typography', 'textIndent' ),
324+
),
318325
'textTransform' => array(
319326
'property_keys' => array(
320327
'default' => 'text-transform',

tests/phpunit/tests/theme/wpThemeJson.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,7 @@ public function test_get_stylesheet() {
579579
'core/post-excerpt' => array(
580580
'typography' => array(
581581
'textColumns' => 2,
582+
'textIndent' => '2em',
582583
),
583584
),
584585
'core/image' => array(
@@ -607,7 +608,7 @@ public function test_get_stylesheet() {
607608
);
608609

609610
$variables = ':root{--wp--preset--color--grey: grey;--wp--preset--gradient--custom-gradient: linear-gradient(135deg,rgba(0,0,0) 0%,rgb(0,0,0) 100%);--wp--preset--font-size--small: 14px;--wp--preset--font-size--big: 41px;--wp--preset--font-family--arial: Arial, serif;--wp--preset--border-radius--small: 2px;--wp--preset--border-radius--medium: 4px;--wp--preset--dimension--small: 100px;--wp--preset--dimension--large: 200px;}.wp-block-group{--wp--custom--base-font: 16;--wp--custom--line-height--small: 1.2;--wp--custom--line-height--medium: 1.4;--wp--custom--line-height--large: 1.8;}';
610-
$styles = ':where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}body{color: var(--wp--preset--color--grey);}a:where(:not(.wp-element-button)){background-color: #333;color: #111;}:root :where(.wp-element-button, .wp-block-button__link){box-shadow: 10px 10px 5px 0px rgba(0,0,0,0.66);}:root :where(.wp-block-cover){min-height: unset;aspect-ratio: 16/9;border-radius: var(--wp--preset--border-radius--small);}:root :where(.wp-block-group){background: var(--wp--preset--gradient--custom-gradient);border-radius: 10px;min-height: 50vh;padding: 24px;height: 500px;width: var(--wp--preset--dimension--large);}:root :where(.wp-block-group a:where(:not(.wp-element-button))){color: #111;}:root :where(.wp-block-heading){color: #123456;}:root :where(.wp-block-heading a:where(:not(.wp-element-button))){background-color: #333;color: #111;font-size: 60px;}:root :where(.wp-block-media-text){text-align: center;}:root :where(.wp-block-post-date){color: #123456;}:root :where(.wp-block-post-date a:where(:not(.wp-element-button))){background-color: #777;color: #555;}:root :where(.wp-block-post-excerpt){column-count: 2;}:root :where(.wp-block-image){margin-bottom: 30px;}:root :where(.wp-block-image img, .wp-block-image .wp-block-image__crop-area, .wp-block-image .components-placeholder){border-top-left-radius: 10px;border-bottom-right-radius: 1em;}:root :where(.wp-block-image img, .wp-block-image .components-placeholder){filter: var(--wp--preset--duotone--custom-duotone);}';
611+
$styles = ':where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}body{color: var(--wp--preset--color--grey);}a:where(:not(.wp-element-button)){background-color: #333;color: #111;}:root :where(.wp-element-button, .wp-block-button__link){box-shadow: 10px 10px 5px 0px rgba(0,0,0,0.66);}:root :where(.wp-block-cover){min-height: unset;aspect-ratio: 16/9;border-radius: var(--wp--preset--border-radius--small);}:root :where(.wp-block-group){background: var(--wp--preset--gradient--custom-gradient);border-radius: 10px;min-height: 50vh;padding: 24px;height: 500px;width: var(--wp--preset--dimension--large);}:root :where(.wp-block-group a:where(:not(.wp-element-button))){color: #111;}:root :where(.wp-block-heading){color: #123456;}:root :where(.wp-block-heading a:where(:not(.wp-element-button))){background-color: #333;color: #111;font-size: 60px;}:root :where(.wp-block-media-text){text-align: center;}:root :where(.wp-block-post-date){color: #123456;}:root :where(.wp-block-post-date a:where(:not(.wp-element-button))){background-color: #777;color: #555;}:root :where(.wp-block-post-excerpt){column-count: 2;text-indent: 2em;}:root :where(.wp-block-image){margin-bottom: 30px;}:root :where(.wp-block-image img, .wp-block-image .wp-block-image__crop-area, .wp-block-image .components-placeholder){border-top-left-radius: 10px;border-bottom-right-radius: 1em;}:root :where(.wp-block-image img, .wp-block-image .components-placeholder){filter: var(--wp--preset--duotone--custom-duotone);}';
611612
$presets = '.has-grey-color{color: var(--wp--preset--color--grey) !important;}.has-grey-background-color{background-color: var(--wp--preset--color--grey) !important;}.has-grey-border-color{border-color: var(--wp--preset--color--grey) !important;}.has-custom-gradient-gradient-background{background: var(--wp--preset--gradient--custom-gradient) !important;}.has-small-font-size{font-size: var(--wp--preset--font-size--small) !important;}.has-big-font-size{font-size: var(--wp--preset--font-size--big) !important;}.has-arial-font-family{font-family: var(--wp--preset--font-family--arial) !important;}';
612613
$all = $variables . $styles . $presets;
613614

0 commit comments

Comments
 (0)