Created
March 19, 2026 12:20
-
-
Save mathetos/ce960a5e891f68392e18293f25327739 to your computer and use it in GitHub Desktop.
Grid Gap Control for WordPress Core Grid Block
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Grid Gap Width Control — adds per-block gap controls to core/group Grid layout. | |
| * | |
| * Drop into Fluent Snippets (or any code-snippets plugin). | |
| * Writes to native style.spacing.blockGap so Core handles frontend output. | |
| */ | |
| add_action( 'enqueue_block_editor_assets', function () { | |
| $handle = 'np-grid-gap-control'; | |
| wp_register_script( | |
| $handle, | |
| false, | |
| array( | |
| 'wp-block-editor', | |
| 'wp-blocks', | |
| 'wp-components', | |
| 'wp-compose', | |
| 'wp-data', | |
| 'wp-element', | |
| 'wp-hooks', | |
| 'wp-i18n', | |
| ), | |
| '1.0.0', | |
| true | |
| ); | |
| wp_enqueue_script( $handle ); | |
| ob_start(); | |
| ?> | |
| ( function( wp ) { | |
| if ( ! wp || ! wp.hooks || ! wp.compose || ! wp.element ) { | |
| return; | |
| } | |
| var addFilter = wp.hooks.addFilter; | |
| var createHOC = wp.compose.createHigherOrderComponent; | |
| var Fragment = wp.element.Fragment; | |
| var el = wp.element.createElement; | |
| var InspectorControls = wp.blockEditor.InspectorControls; | |
| var PanelBody = wp.components.PanelBody; | |
| var ToggleControl = wp.components.ToggleControl; | |
| var Flex = wp.components.Flex; | |
| var FlexItem = wp.components.FlexItem; | |
| var Button = wp.components.Button; | |
| var UnitControl = wp.components.UnitControl || wp.components.__experimentalUnitControl; | |
| var __ = wp.i18n.__; | |
| if ( ! InspectorControls || ! PanelBody || ! UnitControl ) { | |
| return; | |
| } | |
| /* Pull theme spacing units when available. */ | |
| function getUnits() { | |
| var fallback = [ | |
| { value: 'px', label: 'px' }, | |
| { value: 'rem', label: 'rem' }, | |
| { value: 'em', label: 'em' }, | |
| { value: '%', label: '%' }, | |
| { value: 'vw', label: 'vw' }, | |
| ]; | |
| try { | |
| var settings = wp.data.select( 'core/block-editor' ).getSettings(); | |
| var units = settings && settings.spacing && Array.isArray( settings.spacing.units ) | |
| ? settings.spacing.units : null; | |
| if ( ! units || ! units.length ) { return fallback; } | |
| return units.map( function( u ) { | |
| if ( typeof u === 'string' ) { return { value: u, label: u }; } | |
| if ( u && typeof u.value === 'string' ) { return { value: u.value, label: u.label || u.value }; } | |
| return null; | |
| } ).filter( Boolean ); | |
| } catch ( e ) { | |
| return fallback; | |
| } | |
| } | |
| function isGrid( props ) { | |
| var layout = props.attributes && props.attributes.layout ? props.attributes.layout : {}; | |
| return props.name === 'core/group' && layout.type === 'grid'; | |
| } | |
| function gapState( attributes ) { | |
| var gap = ( ( attributes.style || {} ).spacing || {} ).blockGap; | |
| if ( typeof gap === 'string' ) { | |
| return { linked: true, gap: gap, row: gap, col: gap }; | |
| } | |
| if ( gap && typeof gap === 'object' ) { | |
| return { | |
| linked: false, | |
| gap: '', | |
| row: typeof gap.top === 'string' ? gap.top : '', | |
| col: typeof gap.left === 'string' ? gap.left : '', | |
| }; | |
| } | |
| return { linked: true, gap: '', row: '', col: '' }; | |
| } | |
| function setGap( props, next ) { | |
| var style = Object.assign( {}, props.attributes.style || {} ); | |
| var spacing = Object.assign( {}, style.spacing || {} ); | |
| if ( ! next || ( typeof next === 'object' && ! next.top && ! next.left ) ) { | |
| delete spacing.blockGap; | |
| } else { | |
| spacing.blockGap = next; | |
| } | |
| if ( Object.keys( spacing ).length ) { | |
| style.spacing = spacing; | |
| } else { | |
| delete style.spacing; | |
| } | |
| props.setAttributes( { style: style } ); | |
| } | |
| var withGridGap = createHOC( function( BlockEdit ) { | |
| return function( props ) { | |
| if ( ! isGrid( props ) ) { | |
| return el( BlockEdit, props ); | |
| } | |
| var s = gapState( props.attributes ); | |
| var units = getUnits(); | |
| return el( | |
| Fragment, null, | |
| el( BlockEdit, props ), | |
| props.isSelected && el( | |
| InspectorControls, { group: 'settings' }, | |
| el( PanelBody, { title: __( 'Grid Gap' ), initialOpen: true }, | |
| el( ToggleControl, { | |
| label: __( 'Separate row & column gaps' ), | |
| checked: ! s.linked, | |
| onChange: function( on ) { | |
| if ( on ) { | |
| setGap( props, { top: s.row || s.gap || '', left: s.col || s.gap || '' } ); | |
| } else { | |
| setGap( props, s.row || s.col || s.gap || '' ); | |
| } | |
| }, | |
| } ), | |
| s.linked && el( UnitControl, { | |
| label: __( 'Gap width' ), | |
| value: s.gap, | |
| onChange: function( v ) { setGap( props, v || '' ); }, | |
| units: units, | |
| min: 0, | |
| step: 0.1, | |
| } ), | |
| ! s.linked && el( Fragment, null, | |
| el( UnitControl, { | |
| label: __( 'Row gap' ), | |
| value: s.row, | |
| onChange: function( v ) { setGap( props, { top: v || '', left: s.col || '' } ); }, | |
| units: units, | |
| min: 0, | |
| step: 0.1, | |
| } ), | |
| el( UnitControl, { | |
| label: __( 'Column gap' ), | |
| value: s.col, | |
| onChange: function( v ) { setGap( props, { top: s.row || '', left: v || '' } ); }, | |
| units: units, | |
| min: 0, | |
| step: 0.1, | |
| } ) | |
| ), | |
| el( Flex, { justify: 'flex-end' }, | |
| el( FlexItem, null, | |
| el( Button, { | |
| variant: 'tertiary', | |
| onClick: function() { setGap( props, '' ); }, | |
| }, __( 'Reset' ) ) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ); | |
| }; | |
| }, 'npGridGapControl' ); | |
| addFilter( 'editor.BlockEdit', 'np/grid-gap-control', withGridGap ); | |
| } )( window.wp ); | |
| <?php | |
| wp_add_inline_script( $handle, ob_get_clean() ); | |
| } ); | |
| /** | |
| * Enable blockGap support so Core outputs gap CSS for grid/flex layouts. | |
| * Only needed when the active theme doesn't declare it in theme.json. | |
| */ | |
| add_filter( 'wp_theme_json_data_theme', function ( $theme_json ) { | |
| $data = $theme_json->get_data(); | |
| if ( empty( $data['settings']['spacing']['blockGap'] ) ) { | |
| $data['settings']['spacing']['blockGap'] = true; | |
| $theme_json->update_with( $data ); | |
| } | |
| return $theme_json; | |
| } ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment