From 37a48d2e2c3515a28335267189755a4c30ba1612 Mon Sep 17 00:00:00 2001
From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com>
Date: Fri, 3 Mar 2023 16:08:54 +1000
Subject: [PATCH] Add text columns typography block support
---
docs/how-to-guides/themes/theme-json.md | 2 +
.../theme-json-reference/theme-json-living.md | 2 +
.../theme-json-migrations.md | 2 +
lib/block-supports/typography.php | 8 +
lib/class-wp-theme-json-gutenberg.php | 3 +
lib/compat/wordpress-6.3/theme.json | 428 ++++++++++++++++++
.../src/components/global-styles/hooks.js | 13 +-
.../global-styles/typography-panel.js | 49 +-
packages/block-editor/src/hooks/supports.js | 6 +
packages/block-editor/src/hooks/test/style.js | 3 +-
.../src/hooks/test/use-typography-props.js | 2 +
.../block-editor/src/hooks/text-columns.js | 109 +++++
packages/block-editor/src/hooks/typography.js | 2 +
packages/block-editor/src/hooks/utils.js | 3 +
packages/blocks/src/api/constants.js | 5 +
.../blocks/src/store/private-selectors.js | 5 +
phpunit/class-wp-theme-json-test.php | 15 +-
schemas/json/theme.json | 9 +
18 files changed, 658 insertions(+), 8 deletions(-)
create mode 100644 lib/compat/wordpress-6.3/theme.json
create mode 100644 packages/block-editor/src/hooks/text-columns.js
diff --git a/docs/how-to-guides/themes/theme-json.md b/docs/how-to-guides/themes/theme-json.md
index 8e2b60a7693eb6..be7489d78c05e0 100644
--- a/docs/how-to-guides/themes/theme-json.md
+++ b/docs/how-to-guides/themes/theme-json.md
@@ -289,6 +289,7 @@ The settings section has the following structure:
"fontWeight": true,
"letterSpacing": true,
"lineHeight": false,
+ "textColumns": false,
"textDecoration": true,
"textTransform": true
},
@@ -824,6 +825,7 @@ Each block declares which style properties it exposes via the [block supports me
"fontWeight": "value",
"letterSpacing": "value",
"lineHeight": "value",
+ "textColumns": "value",
"textDecoration": "value",
"textTransform": "value"
},
diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md
index acc0a499ec61b0..c8a59c5e10f0c0 100644
--- a/docs/reference-guides/theme-json-reference/theme-json-living.md
+++ b/docs/reference-guides/theme-json-reference/theme-json-living.md
@@ -134,6 +134,7 @@ Settings related to typography.
| fluid | undefined | false | |
| letterSpacing | boolean | true | |
| lineHeight | boolean | false | |
+| textColumns | boolean | false | |
| textDecoration | boolean | true | |
| textTransform | boolean | true | |
| dropCap | boolean | true | |
@@ -204,6 +205,7 @@ Typography styles.
| fontWeight | string, object | |
| letterSpacing | string, object | |
| lineHeight | string, object | |
+| textColumns | string | |
| textDecoration | string, object | |
| textTransform | string, object | |
diff --git a/docs/reference-guides/theme-json-reference/theme-json-migrations.md b/docs/reference-guides/theme-json-reference/theme-json-migrations.md
index cac253f64ffbe9..b043ca1fba52ac 100644
--- a/docs/reference-guides/theme-json-reference/theme-json-migrations.md
+++ b/docs/reference-guides/theme-json-reference/theme-json-migrations.md
@@ -41,6 +41,7 @@ Additions to settings:
- `settings.typography.fontStyle`
- `settings.typography.fontWeight`
- `settings.typography.letterSpacing`
+- `settings.typography.textColumns`
- `settings.typography.textDecoration`
- `settings.typography.textTransform`
@@ -55,6 +56,7 @@ Additions to styles:
- `styles.typography.fontStyle`
- `styles.typography.fontWeight`
- `styles.typography.letterSpacing`
+- `styles.typography.textColumns`
- `styles.typography.textDecoration`
- `styles.typography.textTransform`
diff --git a/lib/block-supports/typography.php b/lib/block-supports/typography.php
index c2e033eb176778..a7a26423e32d6a 100644
--- a/lib/block-supports/typography.php
+++ b/lib/block-supports/typography.php
@@ -26,6 +26,7 @@ function gutenberg_register_typography_support( $block_type ) {
$has_font_weight_support = _wp_array_get( $typography_supports, array( '__experimentalFontWeight' ), false );
$has_letter_spacing_support = _wp_array_get( $typography_supports, array( '__experimentalLetterSpacing' ), false );
$has_line_height_support = _wp_array_get( $typography_supports, array( 'lineHeight' ), false );
+ $has_text_columns_support = _wp_array_get( $typography_supports, array( 'textColumns' ), false );
$has_text_decoration_support = _wp_array_get( $typography_supports, array( '__experimentalTextDecoration' ), false );
$has_text_transform_support = _wp_array_get( $typography_supports, array( '__experimentalTextTransform' ), false );
@@ -35,6 +36,7 @@ function gutenberg_register_typography_support( $block_type ) {
|| $has_font_weight_support
|| $has_letter_spacing_support
|| $has_line_height_support
+ || $has_text_columns_support
|| $has_text_decoration_support
|| $has_text_transform_support;
@@ -91,6 +93,7 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) {
$has_font_weight_support = _wp_array_get( $typography_supports, array( '__experimentalFontWeight' ), false );
$has_letter_spacing_support = _wp_array_get( $typography_supports, array( '__experimentalLetterSpacing' ), false );
$has_line_height_support = _wp_array_get( $typography_supports, array( 'lineHeight' ), false );
+ $has_text_columns_support = _wp_array_get( $typography_supports, array( 'textColumns' ), false );
$has_text_decoration_support = _wp_array_get( $typography_supports, array( '__experimentalTextDecoration' ), false );
$has_text_transform_support = _wp_array_get( $typography_supports, array( '__experimentalTextTransform' ), false );
@@ -100,6 +103,7 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) {
$should_skip_font_style = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'fontStyle' );
$should_skip_font_weight = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'fontWeight' );
$should_skip_line_height = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'lineHeight' );
+ $should_skip_text_columns = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textColumns' );
$should_skip_text_decoration = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textDecoration' );
$should_skip_text_transform = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'textTransform' );
$should_skip_letter_spacing = wp_should_skip_block_supports_serialization( $block_type, 'typography', 'letterSpacing' );
@@ -135,6 +139,10 @@ function gutenberg_apply_typography_support( $block_type, $block_attributes ) {
$typography_block_styles['lineHeight'] = _wp_array_get( $block_attributes, array( 'style', 'typography', 'lineHeight' ), null );
}
+ if ( $has_text_columns_support && ! $should_skip_text_columns && isset( $block_attributes['style']['typography']['textColumns'] ) ) {
+ $typography_block_styles['textColumns'] = _wp_array_get( $block_attributes, array( 'style', 'typography', 'textColumns' ), null );
+ }
+
if ( $has_text_decoration_support && ! $should_skip_text_decoration && isset( $block_attributes['style']['typography']['textDecoration'] ) ) {
$typography_block_styles['textDecoration'] =
gutenberg_typography_get_preset_inline_style_value( $block_attributes['style']['typography']['textDecoration'], 'text-decoration' );
diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php
index 4597ae059f05af..b71a7c95457418 100644
--- a/lib/class-wp-theme-json-gutenberg.php
+++ b/lib/class-wp-theme-json-gutenberg.php
@@ -229,6 +229,7 @@ class WP_Theme_JSON_Gutenberg {
'border-left-width' => array( 'border', 'left', 'width' ),
'border-left-style' => array( 'border', 'left', 'style' ),
'color' => array( 'color', 'text' ),
+ 'column-count' => array( 'typography', 'textColumns' ),
'font-family' => array( 'typography', 'fontFamily' ),
'font-size' => array( 'typography', 'fontSize' ),
'font-style' => array( 'typography', 'fontStyle' ),
@@ -396,6 +397,7 @@ class WP_Theme_JSON_Gutenberg {
'fontWeight' => null,
'letterSpacing' => null,
'lineHeight' => null,
+ 'textColumns' => null,
'textDecoration' => null,
'textTransform' => null,
),
@@ -456,6 +458,7 @@ class WP_Theme_JSON_Gutenberg {
'fontWeight' => null,
'letterSpacing' => null,
'lineHeight' => null,
+ 'textColumns' => null,
'textDecoration' => null,
'textTransform' => null,
),
diff --git a/lib/compat/wordpress-6.3/theme.json b/lib/compat/wordpress-6.3/theme.json
new file mode 100644
index 00000000000000..9437225fde4195
--- /dev/null
+++ b/lib/compat/wordpress-6.3/theme.json
@@ -0,0 +1,428 @@
+{
+ "version": 2,
+ "settings": {
+ "appearanceTools": false,
+ "useRootPaddingAwareAlignments": false,
+ "border": {
+ "color": false,
+ "radius": false,
+ "style": false,
+ "width": false
+ },
+ "color": {
+ "background": true,
+ "custom": true,
+ "customDuotone": true,
+ "customGradient": true,
+ "defaultDuotone": true,
+ "defaultGradients": true,
+ "defaultPalette": true,
+ "duotone": [
+ {
+ "name": "Dark grayscale",
+ "colors": [ "#000000", "#7f7f7f" ],
+ "slug": "dark-grayscale"
+ },
+ {
+ "name": "Grayscale",
+ "colors": [ "#000000", "#ffffff" ],
+ "slug": "grayscale"
+ },
+ {
+ "name": "Purple and yellow",
+ "colors": [ "#8c00b7", "#fcff41" ],
+ "slug": "purple-yellow"
+ },
+ {
+ "name": "Blue and red",
+ "colors": [ "#000097", "#ff4747" ],
+ "slug": "blue-red"
+ },
+ {
+ "name": "Midnight",
+ "colors": [ "#000000", "#00a5ff" ],
+ "slug": "midnight"
+ },
+ {
+ "name": "Magenta and yellow",
+ "colors": [ "#c7005a", "#fff278" ],
+ "slug": "magenta-yellow"
+ },
+ {
+ "name": "Purple and green",
+ "colors": [ "#a60072", "#67ff66" ],
+ "slug": "purple-green"
+ },
+ {
+ "name": "Blue and orange",
+ "colors": [ "#1900d8", "#ffa96b" ],
+ "slug": "blue-orange"
+ }
+ ],
+ "gradients": [
+ {
+ "name": "Vivid cyan blue to vivid purple",
+ "gradient": "linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)",
+ "slug": "vivid-cyan-blue-to-vivid-purple"
+ },
+ {
+ "name": "Light green cyan to vivid green cyan",
+ "gradient": "linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%)",
+ "slug": "light-green-cyan-to-vivid-green-cyan"
+ },
+ {
+ "name": "Luminous vivid amber to luminous vivid orange",
+ "gradient": "linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%)",
+ "slug": "luminous-vivid-amber-to-luminous-vivid-orange"
+ },
+ {
+ "name": "Luminous vivid orange to vivid red",
+ "gradient": "linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%)",
+ "slug": "luminous-vivid-orange-to-vivid-red"
+ },
+ {
+ "name": "Very light gray to cyan bluish gray",
+ "gradient": "linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%)",
+ "slug": "very-light-gray-to-cyan-bluish-gray"
+ },
+ {
+ "name": "Cool to warm spectrum",
+ "gradient": "linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%)",
+ "slug": "cool-to-warm-spectrum"
+ },
+ {
+ "name": "Blush light purple",
+ "gradient": "linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%)",
+ "slug": "blush-light-purple"
+ },
+ {
+ "name": "Blush bordeaux",
+ "gradient": "linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%)",
+ "slug": "blush-bordeaux"
+ },
+ {
+ "name": "Luminous dusk",
+ "gradient": "linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)",
+ "slug": "luminous-dusk"
+ },
+ {
+ "name": "Pale ocean",
+ "gradient": "linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%)",
+ "slug": "pale-ocean"
+ },
+ {
+ "name": "Electric grass",
+ "gradient": "linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%)",
+ "slug": "electric-grass"
+ },
+ {
+ "name": "Midnight",
+ "gradient": "linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%)",
+ "slug": "midnight"
+ }
+ ],
+ "link": false,
+ "palette": [
+ {
+ "name": "Black",
+ "slug": "black",
+ "color": "#000000"
+ },
+ {
+ "name": "Cyan bluish gray",
+ "slug": "cyan-bluish-gray",
+ "color": "#abb8c3"
+ },
+ {
+ "name": "White",
+ "slug": "white",
+ "color": "#ffffff"
+ },
+ {
+ "name": "Pale pink",
+ "slug": "pale-pink",
+ "color": "#f78da7"
+ },
+ {
+ "name": "Vivid red",
+ "slug": "vivid-red",
+ "color": "#cf2e2e"
+ },
+ {
+ "name": "Luminous vivid orange",
+ "slug": "luminous-vivid-orange",
+ "color": "#ff6900"
+ },
+ {
+ "name": "Luminous vivid amber",
+ "slug": "luminous-vivid-amber",
+ "color": "#fcb900"
+ },
+ {
+ "name": "Light green cyan",
+ "slug": "light-green-cyan",
+ "color": "#7bdcb5"
+ },
+ {
+ "name": "Vivid green cyan",
+ "slug": "vivid-green-cyan",
+ "color": "#00d084"
+ },
+ {
+ "name": "Pale cyan blue",
+ "slug": "pale-cyan-blue",
+ "color": "#8ed1fc"
+ },
+ {
+ "name": "Vivid cyan blue",
+ "slug": "vivid-cyan-blue",
+ "color": "#0693e3"
+ },
+ {
+ "name": "Vivid purple",
+ "slug": "vivid-purple",
+ "color": "#9b51e0"
+ }
+ ],
+ "text": true
+ },
+ "layout": {
+ "definitions": {
+ "default": {
+ "name": "default",
+ "slug": "flow",
+ "className": "is-layout-flow",
+ "baseStyles": [
+ {
+ "selector": " > .alignleft",
+ "rules": {
+ "float": "left",
+ "margin-inline-start": "0",
+ "margin-inline-end": "2em"
+ }
+ },
+ {
+ "selector": " > .alignright",
+ "rules": {
+ "float": "right",
+ "margin-inline-start": "2em",
+ "margin-inline-end": "0"
+ }
+ },
+ {
+ "selector": " > .aligncenter",
+ "rules": {
+ "margin-left": "auto !important",
+ "margin-right": "auto !important"
+ }
+ }
+ ],
+ "spacingStyles": [
+ {
+ "selector": " > *",
+ "rules": {
+ "margin-block-start": "0",
+ "margin-block-end": "0"
+ }
+ },
+ {
+ "selector": " > * + *",
+ "rules": {
+ "margin-block-start": null,
+ "margin-block-end": "0"
+ }
+ }
+ ]
+ },
+ "constrained": {
+ "name": "constrained",
+ "slug": "constrained",
+ "className": "is-layout-constrained",
+ "baseStyles": [
+ {
+ "selector": " > .alignleft",
+ "rules": {
+ "float": "left",
+ "margin-inline-start": "0",
+ "margin-inline-end": "2em"
+ }
+ },
+ {
+ "selector": " > .alignright",
+ "rules": {
+ "float": "right",
+ "margin-inline-start": "2em",
+ "margin-inline-end": "0"
+ }
+ },
+ {
+ "selector": " > .aligncenter",
+ "rules": {
+ "margin-left": "auto !important",
+ "margin-right": "auto !important"
+ }
+ },
+ {
+ "selector": " > :where(:not(.alignleft):not(.alignright):not(.alignfull))",
+ "rules": {
+ "max-width": "var(--wp--style--global--content-size)",
+ "margin-left": "auto !important",
+ "margin-right": "auto !important"
+ }
+ },
+ {
+ "selector": " > .alignwide",
+ "rules": {
+ "max-width": "var(--wp--style--global--wide-size)"
+ }
+ }
+ ],
+ "spacingStyles": [
+ {
+ "selector": " > *",
+ "rules": {
+ "margin-block-start": "0",
+ "margin-block-end": "0"
+ }
+ },
+ {
+ "selector": " > * + *",
+ "rules": {
+ "margin-block-start": null,
+ "margin-block-end": "0"
+ }
+ }
+ ]
+ },
+ "flex": {
+ "name": "flex",
+ "slug": "flex",
+ "className": "is-layout-flex",
+ "displayMode": "flex",
+ "baseStyles": [
+ {
+ "selector": "",
+ "rules": {
+ "flex-wrap": "wrap",
+ "align-items": "center"
+ }
+ },
+ {
+ "selector": " > *",
+ "rules": {
+ "margin": "0"
+ }
+ }
+ ],
+ "spacingStyles": [
+ {
+ "selector": "",
+ "rules": {
+ "gap": null
+ }
+ }
+ ]
+ }
+ }
+ },
+ "spacing": {
+ "blockGap": null,
+ "margin": false,
+ "minHeight": false,
+ "padding": false,
+ "customSpacingSize": true,
+ "units": [ "px", "em", "rem", "vh", "vw", "%" ],
+ "spacingScale": {
+ "operator": "*",
+ "increment": 1.5,
+ "steps": 7,
+ "mediumStep": 1.5,
+ "unit": "rem"
+ }
+ },
+ "typography": {
+ "customFontSize": true,
+ "dropCap": true,
+ "fontSizes": [
+ {
+ "name": "Small",
+ "slug": "small",
+ "size": "13px"
+ },
+ {
+ "name": "Medium",
+ "slug": "medium",
+ "size": "20px"
+ },
+ {
+ "name": "Large",
+ "slug": "large",
+ "size": "36px"
+ },
+ {
+ "name": "Extra Large",
+ "slug": "x-large",
+ "size": "42px"
+ }
+ ],
+ "fontStyle": true,
+ "fontWeight": true,
+ "letterSpacing": true,
+ "lineHeight": false,
+ "textColumns": false,
+ "textDecoration": true,
+ "textTransform": true
+ },
+ "blocks": {
+ "core/button": {
+ "border": {
+ "radius": true
+ }
+ },
+ "core/pullquote": {
+ "border": {
+ "color": true,
+ "radius": true,
+ "style": true,
+ "width": true
+ }
+ }
+ }
+ },
+ "styles": {
+ "elements": {
+ "button": {
+ "color": {
+ "text": "#fff",
+ "background": "#32373c"
+ },
+ "spacing": {
+ "padding": "calc(0.667em + 2px) calc(1.333em + 2px)"
+ },
+ "typography": {
+ "fontSize": "inherit",
+ "fontFamily": "inherit",
+ "lineHeight": "inherit",
+ "textDecoration": "none"
+ },
+ "border": {
+ "width": "0"
+ }
+ },
+ "link": {
+ "typography": {
+ "textDecoration": "underline"
+ }
+ }
+ },
+ "spacing": {
+ "blockGap": "24px",
+ "padding": {
+ "top": "0px",
+ "right": "0px",
+ "bottom": "0px",
+ "left": "0px"
+ }
+ }
+ }
+}
diff --git a/packages/block-editor/src/components/global-styles/hooks.js b/packages/block-editor/src/components/global-styles/hooks.js
index 2414f3d4b4b9c7..c169cb03a60836 100644
--- a/packages/block-editor/src/components/global-styles/hooks.js
+++ b/packages/block-editor/src/components/global-styles/hooks.js
@@ -65,6 +65,7 @@ const VALID_SETTINGS = [
'typography.fontWeight',
'typography.letterSpacing',
'typography.lineHeight',
+ 'typography.textColumns',
'typography.textDecoration',
'typography.textTransform',
];
@@ -178,7 +179,7 @@ export function useGlobalStyle(
switch ( source ) {
case 'all':
rawResult =
- // The stlyes.css path is allowed to be empty, so don't revert to base if undefined.
+ // The styles.css path is allowed to be empty, so don't revert to base if undefined.
finalPath === 'styles.css'
? get( userConfig, finalPath )
: get( mergedConfig, finalPath );
@@ -266,6 +267,16 @@ export function useSettingsForBlockElement(
}
} );
+ // The column-count style is named text column to reduce confusion with
+ // the columns block and manage expectations from the support.
+ // See: https://github.com/WordPress/gutenberg/pull/33587
+ if ( ! supportedStyles.includes( 'columnCount' ) ) {
+ updatedSettings.typography = {
+ ...updatedSettings.typography,
+ textColumns: false,
+ };
+ }
+
[ 'contentSize', 'wideSize' ].forEach( ( key ) => {
if ( ! supportedStyles.includes( key ) ) {
updatedSettings.layout = {
diff --git a/packages/block-editor/src/components/global-styles/typography-panel.js b/packages/block-editor/src/components/global-styles/typography-panel.js
index 9a928e30227f8f..1c3c253c7b69db 100644
--- a/packages/block-editor/src/components/global-styles/typography-panel.js
+++ b/packages/block-editor/src/components/global-styles/typography-panel.js
@@ -3,6 +3,7 @@
*/
import {
FontSizePicker,
+ __experimentalNumberControl as NumberControl,
__experimentalToolsPanel as ToolsPanel,
__experimentalToolsPanelItem as ToolsPanelItem,
} from '@wordpress/components';
@@ -20,6 +21,9 @@ import TextTransformControl from '../text-transform-control';
import TextDecorationControl from '../text-decoration-control';
import { getValueFromVariable } from './utils';
+const MIN_TEXT_COLUMNS = 1;
+const MAX_TEXT_COLUMNS = 6;
+
export function useHasTypographyPanel( settings ) {
const hasFontFamily = useHasFontFamilyControl( settings );
const hasLineHeight = useHasLineHeightControl( settings );
@@ -27,6 +31,7 @@ export function useHasTypographyPanel( settings ) {
const hasLetterSpacing = useHasLetterSpacingControl( settings );
const hasTextTransform = useHasTextTransformControl( settings );
const hasTextDecoration = useHasTextDecorationControl( settings );
+ const hasTextColumns = useHasTextColumnsControl( settings );
const hasFontSize = useHasFontSizeControl( settings );
return (
@@ -36,7 +41,8 @@ export function useHasTypographyPanel( settings ) {
hasLetterSpacing ||
hasTextTransform ||
hasFontSize ||
- hasTextDecoration
+ hasTextDecoration ||
+ hasTextColumns
);
}
@@ -93,6 +99,10 @@ function useHasTextDecorationControl( settings ) {
return settings?.typography?.textDecoration;
}
+function useHasTextColumnsControl( settings ) {
+ return settings?.typography?.textColumns;
+}
+
function TypographyToolsPanel( {
resetAllFilter,
onChange,
@@ -124,6 +134,7 @@ const DEFAULT_CONTROLS = {
letterSpacing: true,
textTransform: true,
textDecoration: true,
+ textColumns: true,
};
export default function TypographyPanel( {
@@ -246,6 +257,21 @@ export default function TypographyPanel( {
const hasLetterSpacing = () => !! value?.typography?.letterSpacing;
const resetLetterSpacing = () => setLetterSpacing( undefined );
+ // Text Columns
+ const hasTextColumnsControl = useHasTextColumnsControl( settings );
+ const textColumns = decodeValue( inheritedValue?.typography?.textColumns );
+ const setTextColumns = ( newValue ) => {
+ onChange( {
+ ...value,
+ typography: {
+ ...value?.typography,
+ textColumns: newValue,
+ },
+ } );
+ };
+ const hasTextColumns = () => !! value?.typography?.textColumns;
+ const resetTextColumns = () => setTextColumns( undefined );
+
// Text Transform
const hasTextTransformControl = useHasTextTransformControl( settings );
const textTransform = decodeValue(
@@ -388,6 +414,27 @@ export default function TypographyPanel( {
/>
) }
+ { hasTextColumnsControl && (
+
+
+
+ ) }
{ hasTextDecorationControl && (
{
expect(
getInlineStyles( {
color: { text: 'red', background: 'black' },
- typography: { lineHeight: 1.5, fontSize: 10 },
+ typography: { lineHeight: 1.5, fontSize: 10, textColumns: 2 },
border: {
radius: '10px',
width: '1em',
@@ -44,6 +44,7 @@ describe( 'getInlineStyles', () => {
borderStyle: 'dotted',
borderWidth: '1em',
color: 'red',
+ columnCount: 2,
lineHeight: 1.5,
fontSize: 10,
marginBottom: '15px',
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 12336eb2c44afd..9b43a074cfcbaf 100644
--- a/packages/block-editor/src/hooks/test/use-typography-props.js
+++ b/packages/block-editor/src/hooks/test/use-typography-props.js
@@ -12,6 +12,7 @@ describe( 'getTypographyClassesAndStyles', () => {
typography: {
letterSpacing: '22px',
fontSize: '2rem',
+ textColumns: 3,
textTransform: 'uppercase',
},
},
@@ -19,6 +20,7 @@ describe( 'getTypographyClassesAndStyles', () => {
expect( getTypographyClassesAndStyles( attributes ) ).toEqual( {
className: 'has-tofu-font-family has-large-font-size',
style: {
+ columnCount: 3,
letterSpacing: '22px',
fontSize: '2rem',
textTransform: 'uppercase',
diff --git a/packages/block-editor/src/hooks/text-columns.js b/packages/block-editor/src/hooks/text-columns.js
new file mode 100644
index 00000000000000..17afd3a629f61d
--- /dev/null
+++ b/packages/block-editor/src/hooks/text-columns.js
@@ -0,0 +1,109 @@
+/**
+ * WordPress dependencies
+ */
+import { hasBlockSupport } from '@wordpress/blocks';
+import { __experimentalNumberControl as NumberControl } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import useSetting from '../components/use-setting';
+import { cleanEmptyObject } from './utils';
+
+/**
+ * Key within block settings' supports array indicating support for text
+ * columns e.g. settings found in `block.json`.
+ */
+export const TEXT_COLUMNS_SUPPORT_KEY = 'typography.textColumns';
+
+const MIN_COLUMNS = 1;
+const MAX_COLUMNS = 6;
+
+/**
+ * Inspector control containing the text columns option.
+ *
+ * @param {Object} props Block properties.
+ *
+ * @return {WPElement} Text columns edit element.
+ */
+export function TextColumnsEdit( props ) {
+ const {
+ attributes: { style },
+ setAttributes,
+ } = props;
+
+ function onChange( newColumns ) {
+ setAttributes( {
+ style: cleanEmptyObject( {
+ ...style,
+ typography: {
+ ...style?.typography,
+ textColumns: newColumns,
+ },
+ } ),
+ } );
+ }
+
+ return (
+
+ );
+}
+
+/**
+ * Checks if text columns setting has been disabled.
+ *
+ * @param {string} name Name of the block.
+ *
+ * @return {boolean} Whether or not the setting is disabled.
+ */
+export function useIsTextColumnsDisabled( { name: blockName } = {} ) {
+ const notSupported = ! hasBlockSupport(
+ blockName,
+ TEXT_COLUMNS_SUPPORT_KEY
+ );
+ const hasTextColumns = useSetting( 'typography.textColumns' );
+ return notSupported || ! hasTextColumns;
+}
+
+/**
+ * Checks if there is a current value set for the text columns block support.
+ *
+ * @param {Object} props Block props.
+ * @return {boolean} Whether or not the block has a text columns set.
+ */
+export function hasTextColumnsValue( props ) {
+ return !! props.attributes.style?.typography?.textColumns;
+}
+
+/**
+ * Resets the text columns block support attribute. This can be used when
+ * disabling the text columns support controls for a block via a progressive
+ * discovery panel.
+ *
+ * @param {Object} props Block props.
+ * @param {Object} props.attributes Block's attributes.
+ * @param {Object} props.setAttributes Function to set block's attributes.
+ */
+export function resetTextColumns( { attributes = {}, setAttributes } ) {
+ const { style } = attributes;
+
+ setAttributes( {
+ style: cleanEmptyObject( {
+ ...style,
+ typography: {
+ ...style?.typography,
+ textColumns: undefined,
+ },
+ } ),
+ } );
+}
diff --git a/packages/block-editor/src/hooks/typography.js b/packages/block-editor/src/hooks/typography.js
index c5d3c74852777f..00e1c348f57c80 100644
--- a/packages/block-editor/src/hooks/typography.js
+++ b/packages/block-editor/src/hooks/typography.js
@@ -27,6 +27,7 @@ function omit( object, keys ) {
const LETTER_SPACING_SUPPORT_KEY = 'typography.__experimentalLetterSpacing';
const TEXT_TRANSFORM_SUPPORT_KEY = 'typography.__experimentalTextTransform';
const TEXT_DECORATION_SUPPORT_KEY = 'typography.__experimentalTextDecoration';
+const TEXT_COLUMNS_SUPPORT_KEY = 'typography.textColumns';
const FONT_STYLE_SUPPORT_KEY = 'typography.__experimentalFontStyle';
const FONT_WEIGHT_SUPPORT_KEY = 'typography.__experimentalFontWeight';
export const TYPOGRAPHY_SUPPORT_KEY = 'typography';
@@ -36,6 +37,7 @@ export const TYPOGRAPHY_SUPPORT_KEYS = [
FONT_STYLE_SUPPORT_KEY,
FONT_WEIGHT_SUPPORT_KEY,
FONT_FAMILY_SUPPORT_KEY,
+ TEXT_COLUMNS_SUPPORT_KEY,
TEXT_DECORATION_SUPPORT_KEY,
TEXT_TRANSFORM_SUPPORT_KEY,
LETTER_SPACING_SUPPORT_KEY,
diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js
index 3684598997a6a8..f324bd99bc6eac 100644
--- a/packages/block-editor/src/hooks/utils.js
+++ b/packages/block-editor/src/hooks/utils.js
@@ -202,6 +202,7 @@ export function useBlockSettings( name, parentLayout ) {
const fontStyle = useSetting( 'typography.fontStyle' );
const fontWeight = useSetting( 'typography.fontWeight' );
const lineHeight = useSetting( 'typography.lineHeight' );
+ const textColumns = useSetting( 'typography.textColumns' );
const textDecoration = useSetting( 'typography.textDecoration' );
const textTransform = useSetting( 'typography.textTransform' );
const letterSpacing = useSetting( 'typography.letterSpacing' );
@@ -244,6 +245,7 @@ export function useBlockSettings( name, parentLayout ) {
fontStyle,
fontWeight,
lineHeight,
+ textColumns,
textDecoration,
textTransform,
letterSpacing,
@@ -276,6 +278,7 @@ export function useBlockSettings( name, parentLayout ) {
fontStyle,
fontWeight,
lineHeight,
+ textColumns,
textDecoration,
textTransform,
letterSpacing,
diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js
index 28801b3cf4882e..1ab0b26448c1a5 100644
--- a/packages/blocks/src/api/constants.js
+++ b/packages/blocks/src/api/constants.js
@@ -123,6 +123,11 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = {
requiresOptOut: true,
useEngine: true,
},
+ columnCount: {
+ value: [ 'typography', 'textColumns' ],
+ support: [ 'typography', 'textColumns' ],
+ useEngine: true,
+ },
filter: {
value: [ 'filter', 'duotone' ],
support: [ 'color', '__experimentalDuotone' ],
diff --git a/packages/blocks/src/store/private-selectors.js b/packages/blocks/src/store/private-selectors.js
index 2f4fb1dbea3866..9e38194cd11cb0 100644
--- a/packages/blocks/src/store/private-selectors.js
+++ b/packages/blocks/src/store/private-selectors.js
@@ -72,6 +72,11 @@ function filterElementBlockSupports( blockSupports, name, element ) {
return false;
}
+ // Text columns is only available for blocks.
+ if ( support === 'textColumns' && ! name ) {
+ return false;
+ }
+
return true;
} );
}
diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php
index abb7c26cba866a..742c2d948b4515 100644
--- a/phpunit/class-wp-theme-json-test.php
+++ b/phpunit/class-wp-theme-json-test.php
@@ -388,7 +388,7 @@ public function test_get_stylesheet() {
),
),
'blocks' => array(
- 'core/group' => array(
+ 'core/group' => array(
'border' => array(
'radius' => '10px',
),
@@ -406,7 +406,7 @@ public function test_get_stylesheet() {
'padding' => '24px',
),
),
- 'core/heading' => array(
+ 'core/heading' => array(
'color' => array(
'text' => '#123456',
),
@@ -422,7 +422,7 @@ public function test_get_stylesheet() {
),
),
),
- 'core/post-date' => array(
+ 'core/post-date' => array(
'color' => array(
'text' => '#123456',
),
@@ -435,7 +435,12 @@ public function test_get_stylesheet() {
),
),
),
- 'core/image' => array(
+ 'core/post-excerpt' => array(
+ 'typography' => array(
+ 'textColumns' => 2,
+ ),
+ ),
+ 'core/image' => array(
'border' => array(
'radius' => array(
'topLeft' => '10px',
@@ -455,7 +460,7 @@ public function test_get_stylesheet() {
);
$variables = 'body{--wp--preset--color--grey: grey;--wp--preset--font-family--small: 14px;--wp--preset--font-family--big: 41px;}.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;}';
- $styles = '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; }body{color: var(--wp--preset--color--grey);}a:where(:not(.wp-element-button)){background-color: #333;color: #111;}.wp-block-group{border-radius: 10px;min-height: 50vh;padding: 24px;}.wp-block-group a:where(:not(.wp-element-button)){color: #111;}.wp-block-heading{color: #123456;}.wp-block-heading a:where(:not(.wp-element-button)){background-color: #333;color: #111;font-size: 60px;}.wp-block-post-date{color: #123456;}.wp-block-post-date a:where(:not(.wp-element-button)){background-color: #777;color: #555;}.wp-block-image{margin-bottom: 30px;}.wp-block-image img, .wp-block-image .wp-block-image__crop-area{border-top-left-radius: 10px;border-bottom-right-radius: 1em;}';
+ $styles = '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; }body{color: var(--wp--preset--color--grey);}a:where(:not(.wp-element-button)){background-color: #333;color: #111;}.wp-block-group{border-radius: 10px;min-height: 50vh;padding: 24px;}.wp-block-group a:where(:not(.wp-element-button)){color: #111;}.wp-block-heading{color: #123456;}.wp-block-heading a:where(:not(.wp-element-button)){background-color: #333;color: #111;font-size: 60px;}.wp-block-post-date{color: #123456;}.wp-block-post-date a:where(:not(.wp-element-button)){background-color: #777;color: #555;}.wp-block-post-excerpt{column-count: 2;}.wp-block-image{margin-bottom: 30px;}.wp-block-image img, .wp-block-image .wp-block-image__crop-area{border-top-left-radius: 10px;border-bottom-right-radius: 1em;}';
$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-small-font-family{font-family: var(--wp--preset--font-family--small) !important;}.has-big-font-family{font-family: var(--wp--preset--font-family--big) !important;}';
$all = $variables . $styles . $presets;
diff --git a/schemas/json/theme.json b/schemas/json/theme.json
index d2fb13b6f13b4c..39c4b3ac902000 100644
--- a/schemas/json/theme.json
+++ b/schemas/json/theme.json
@@ -424,6 +424,11 @@
"type": "boolean",
"default": false
},
+ "textColumns": {
+ "description": "Allow users to set the number of text columns.",
+ "type": "boolean",
+ "default": false
+ },
"textDecoration": {
"description": "Allow users to set custom text decorations.",
"type": "boolean",
@@ -1443,6 +1448,10 @@
}
]
},
+ "textColumns": {
+ "description": "Sets the `column-count` CSS property.",
+ "type": "string"
+ },
"textDecoration": {
"description": "Sets the `text-decoration` CSS property.",
"oneOf": [