From 46a64c9617b9296636aa18039e902ab2e2d755ab Mon Sep 17 00:00:00 2001 From: Arukuen Date: Wed, 23 Sep 2026 13:34:42 +0800 Subject: [PATCH 01/10] fix: change the current shadow control following gutenberg's new --- src/components/shadow-control/editor.scss | 65 +++++++++++++ src/components/shadow-control/index.js | 107 +++++++++++++++++----- 2 files changed, 147 insertions(+), 25 deletions(-) diff --git a/src/components/shadow-control/editor.scss b/src/components/shadow-control/editor.scss index 36689af37c..38a13973fe 100644 --- a/src/components/shadow-control/editor.scss +++ b/src/components/shadow-control/editor.scss @@ -11,3 +11,68 @@ align-items: center; } } + +.stk-shadow-control__presets-button { + justify-content: flex-start; + width: 100%; + + .dashicon { + margin-right: 8px; + } +} + +.stk-shadow-control__preset-grid { + display: flex; + flex-wrap: wrap; + gap: 12px; + padding-bottom: 8px; +} + +.stk-shadow-control__preset { + appearance: none; + align-items: center; + background: #fff; + border: 1px solid #e0e0e0; + border-radius: 2px; + box-sizing: border-box; + color: #2f2f2f; + cursor: pointer; + display: inline-flex; + height: 26px; + justify-content: center; + padding: 0; + position: relative; + transform: scale(1); + width: 26px; + + &:hover { + transform: scale(1.2); + } + + &:focus-visible { + border: 2px solid #757575; + outline: none; + } + + &.is-none { + background: linear-gradient(-45deg, transparent 48%, #ddd 48%, #ddd 52%, transparent 52%); + } + + .dashicon { + color: #2f2f2f; + font-size: 20px; + height: 20px; + width: 20px; + } +} + +@media (prefers-reduced-motion: no-preference) { + .stk-shadow-control__preset { + transition: transform 0.1s ease; + } +} + +.stk-shadow-control__clear.components-button { + display: block; + margin: 18px 0 0 auto; +} diff --git a/src/components/shadow-control/index.js b/src/components/shadow-control/index.js index 5f08a652b8..7ed2767a86 100644 --- a/src/components/shadow-control/index.js +++ b/src/components/shadow-control/index.js @@ -20,7 +20,9 @@ import { memo, } from '@wordpress/element' import { applyFilters } from '@wordpress/hooks' -import { Button, Dashicon } from '@wordpress/components' +import { + Button, Dashicon, PanelBody, Tooltip, +} from '@wordpress/components' export const getShadows = () => { return applyFilters( 'stackable.shadows', [ @@ -254,8 +256,9 @@ const ShadowControl = memo( props => { } = props const shadows = options || getShadows() - const buttonRef = useRef( null ) - const [ isPopoverOpen, setIsPopoverOpen ] = useState( false ) + const presetButtonRef = useRef( null ) + const settingsButtonRef = useRef( null ) + const [ openPopover, setOpenPopover ] = useState( '' ) const valueCallback = value => { return value ? shadows.indexOf( value ) === -1 ? 'custom' : shadows.indexOf( value ) : '' @@ -267,63 +270,117 @@ const ShadowControl = memo( props => { const [ _value, onChange ] = useControlHandlers( props.attribute, props.responsive, props.hover, valueCallback, changeCallback ) const value = typeof props.value === 'undefined' ? _value : props.value + const effectiveOnChange = typeof props.onChange === 'undefined' ? onChange : props.onChange + const selectedValue = value === '' ? valueCallback( props.placeholder ) : value const [ propsToPass, controlProps ] = extractControlProps( _props ) useEffect( () => { const clickOutsideListener = event => { - if ( isPopoverOpen ) { + if ( openPopover ) { if ( ! event.target.closest( '.shadow-control__popover' ) && + ! event.target.closest( '.stk-shadow-control__presets-button' ) && ! event.target.closest( '.stk-shadow-control__more-button' ) && ! event.target.closest( '.components-color-picker' ) && ! event.target.closest( '.react-autosuggest__suggestions-container' ) && ! event.target.closest( '.components-dropdown__content' ) ) { - setIsPopoverOpen( false ) + setOpenPopover( '' ) } } } document.body.addEventListener( 'mousedown', clickOutsideListener ) return () => document.body.removeEventListener( 'mousedown', clickOutsideListener ) - }, [ isPopoverOpen ] ) - - useEffect( () => { - if ( isPopoverOpen ) { - } - }, [ value, isPopoverOpen ] ) + }, [ openPopover ] ) return ( <> - setIsPopoverOpen( ! isPopoverOpen ) } + onClick={ () => setOpenPopover( openPopover === 'settings' ? '' : 'settings' ) } icon={ } /> ) } - /> - { isPopoverOpen && ( + > + + + { openPopover === 'presets' && ( + setOpenPopover( '' ) } + > + +

{ __( 'Drop shadow', i18n ) }

+
+ { shadows.map( ( shadow, index ) => { + const isSelected = selectedValue === index + const presetLabel = index === 0 + ? __( 'No shadow', i18n ) + : sprintf( __( 'Shadow %d', i18n ), index ) + + return ( + + + + ) + } ) } +
+ +
+
+ ) } + { openPopover === 'settings' && ( { parentProps={ props } hasInset={ props.hasInset } isFilter={ props.isFilter } - onEscape={ () => setIsPopoverOpen( false ) } + onEscape={ () => setOpenPopover( '' ) } value={ props.shadowFilterValue } onChange={ props.shadowFilterOnChange } /> From 5143b3639463ec9bd6e419b75220528dd4b97635 Mon Sep 17 00:00:00 2001 From: Arukuen Date: Tue, 29 Sep 2026 08:29:42 +0800 Subject: [PATCH 02/10] fix: shadows and border radius --- e2e/readme.md | 2 +- e2e/tests/admin.spec.ts | 1 + src/block-components/typography/edit.js | 6 +- src/components/index.js | 4 +- .../shadow-control/__test__/index.test.js | 32 +++ src/components/shadow-control/editor.scss | 25 ++- src/components/shadow-control/index.js | 184 +++++++++++++----- src/hooks/use-preset-controls.js | 37 ++-- .../preset-controls/editor-loader.js | 26 ++- .../global-settings/preset-controls/index.php | 104 +++++++--- .../preset-controls/presets.json | 125 ++++++++---- .../utils/use-block-layout-inspector-utils.js | 27 ++- src/welcome/admin.js | 10 + 13 files changed, 449 insertions(+), 134 deletions(-) create mode 100644 src/components/shadow-control/__test__/index.test.js diff --git a/e2e/readme.md b/e2e/readme.md index 17829f2137..4584fbead9 100644 --- a/e2e/readme.md +++ b/e2e/readme.md @@ -135,7 +135,7 @@ Fail if a panel still shows `ProControl` / "Get Premium" while premium is mocked | Surface | Flow | |---------|------| -| Design System | Sidebar opens; Preview; Color Schemes / Font pairs / Size presets / Icon Library are live controls | +| Design System | Sidebar opens; Preview; Color Schemes / Font pairs / Size and shadow presets / Icon Library are live controls | | Global Block Styles | Save a named style from one Text block, apply it to a second; canvas uses the style, inspector stays at defaults and can override; rename and delete from Design System; updating a style applies the change to other blocks that use it | | Dynamic Content | Post title / meta / featured image resolve on the frontend; custom date format apply path (`e2e/tests/dynamic-content-custom-date.spec.ts`) | | Conditional display | Logged-in condition visible on frontend; logged-out condition hidden while logged in | diff --git a/e2e/tests/admin.spec.ts b/e2e/tests/admin.spec.ts index d18a648829..fc05779791 100644 --- a/e2e/tests/admin.spec.ts +++ b/e2e/tests/admin.spec.ts @@ -94,6 +94,7 @@ test( 'Settings inner tabs render', async ( { await page.getByRole( 'tab', { name: /Miscellaneous/ } ).click() await expect( page.getByRole( 'heading', { name: 'Miscellaneous' } ) ).toBeVisible() + await expect( page.getByRole( 'checkbox', { name: 'Use Border Radius Presets from theme.json' } ) ).toBeVisible() await page.getByRole( 'tab', { name: 'Import/Export' } ).click() await expect( page.getByRole( 'heading', { name: 'Import' } ) ).toBeVisible() diff --git a/src/block-components/typography/edit.js b/src/block-components/typography/edit.js index 15b46f4f03..60491c86cf 100644 --- a/src/block-components/typography/edit.js +++ b/src/block-components/typography/edit.js @@ -23,7 +23,9 @@ import { PanelAdvancedSettings, ShadowControl, } from '~stackable/components' -import { getAttributeName, getAttrNameFunction } from '~stackable/util' +import { + getAttributeName, getAttrNameFunction, getFontFamily, +} from '~stackable/util' import { escapeHTMLIfInvalid } from './util' /** @@ -303,6 +305,8 @@ export const Controls = props => { label={ __( 'Shadow / Outline', i18n ) } attribute={ attributeName( 'textShadow' ) } options={ TYPOGRAPHY_SHADOWS } + previewType="text" + previewFontFamily={ fontFamily ? getFontFamily( fontFamily ) : undefined } placeholder="" hover="all" hasInset={ false } diff --git a/src/components/index.js b/src/components/index.js index 04d009af78..fa1ebd8485 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -80,7 +80,9 @@ export { default as AdvancedFocalPointControl } from './advanced-focal-point-con export { default as AdvancedTokenField } from './advanced-token-field' export { default as ImageFilterControl } from './image-filter-control' export { default as GroupPlaceholder } from './group-placeholder' -export { default as ShadowControl, getShadows } from './shadow-control' +export { + default as ShadowControl, getGlobalShadowOptions, getShadows, +} from './shadow-control' export { default as LinkControl } from './link-control' export { default as ColumnInserter } from './column-inserter' export { default as StyleControl } from './style-control' diff --git a/src/components/shadow-control/__test__/index.test.js b/src/components/shadow-control/__test__/index.test.js new file mode 100644 index 0000000000..82f74c6b4c --- /dev/null +++ b/src/components/shadow-control/__test__/index.test.js @@ -0,0 +1,32 @@ +import { getShadowFilterValue } from '../' + +describe( 'getShadowFilterValue', () => { + it( 'resolves a selected preset to its raw shadow for advanced editing', () => { + const presets = [ + { shadow: 'none' }, + { shadow: '0 5px 5px 0 #123f5209' }, + ] + + expect( getShadowFilterValue( + 1, + presets, + 'var(--stk--preset--shadow--shadow-3, 0 5px 5px 0 #123f5209)' + ) ).toBe( '0 5px 5px 0 #123f5209' ) + } ) + + it( 'keeps custom shadow values unchanged', () => { + expect( getShadowFilterValue( + 'custom', + [], + '2px 4px 8px 0 #00000040' + ) ).toBe( '2px 4px 8px 0 #00000040' ) + } ) + + it( 'falls back to the stored value when a selected preset is unavailable', () => { + expect( getShadowFilterValue( + 3, + [], + '2px 4px 8px 0 #00000040' + ) ).toBe( '2px 4px 8px 0 #00000040' ) + } ) +} ) diff --git a/src/components/shadow-control/editor.scss b/src/components/shadow-control/editor.scss index 38a13973fe..a0303b8c12 100644 --- a/src/components/shadow-control/editor.scss +++ b/src/components/shadow-control/editor.scss @@ -16,15 +16,16 @@ justify-content: flex-start; width: 100%; - .dashicon { + .stk-shadow-control__presets-icon { margin-right: 8px; } } .stk-shadow-control__preset-grid { - display: flex; - flex-wrap: wrap; - gap: 12px; + display: grid; + grid-template-columns: repeat(4, 44px); + justify-content: space-between; + row-gap: 16px; padding-bottom: 8px; } @@ -38,12 +39,12 @@ color: #2f2f2f; cursor: pointer; display: inline-flex; - height: 26px; + height: 44px; justify-content: center; padding: 0; position: relative; transform: scale(1); - width: 26px; + width: 44px; &:hover { transform: scale(1.2); @@ -66,6 +67,13 @@ } } +.stk-shadow-control__preset-text { + color: #2f2f2f; + font-size: 18px; + line-height: 1; + white-space: nowrap; +} + @media (prefers-reduced-motion: no-preference) { .stk-shadow-control__preset { transition: transform 0.1s ease; @@ -76,3 +84,8 @@ display: block; margin: 18px 0 0 auto; } + +.block-editor-global-styles__shadow-popover-container .block-editor-global-styles__shadow-indicator { + height: 44px; + width: 44px; +} diff --git a/src/components/shadow-control/index.js b/src/components/shadow-control/index.js index 7ed2767a86..316337c913 100644 --- a/src/components/shadow-control/index.js +++ b/src/components/shadow-control/index.js @@ -8,6 +8,9 @@ import { } from '~stackable/components' import AdvancedControl, { extractControlProps } from '~stackable/components/base-control2' import { useControlHandlers } from '~stackable/components/base-control2/hooks' +import { ResetButton } from '~stackable/components/base-control2/reset-button' +import { usePresetControls } from '~stackable/hooks' +import DEFAULT_PRESETS from '~stackable/plugins/global-settings/preset-controls/presets.json' /** * WordPress dependencies @@ -20,23 +23,57 @@ import { memo, } from '@wordpress/element' import { applyFilters } from '@wordpress/hooks' +import { Icon, shadow } from '@wordpress/icons' import { Button, Dashicon, PanelBody, Tooltip, } from '@wordpress/components' -export const getShadows = () => { - return applyFilters( 'stackable.shadows', [ - 'none', - '0 0 0 1px #7878781a', - '0 0 0 2px #7878781a', - '0 5px 5px 0 #123f5209', - '0px 2px 20px #99999933', - '0 5px 30px -10px #123f524d', - '0px 10px 30px #0000000d', - '7px 5px 30px #48497927', - '0px 10px 60px #0000001a', - '0px 70px 90px -20px #4849794d', - ] ) +/** + * Return the raw CSS shadow values used by the preset picker. + * + * `none` is a Stackable control option rather than a theme.json preset. + * The filter remains available for extensions that append or replace shadows. + * + * @param {string[]} shadows Raw CSS shadow values. + * @return {string[]} Filtered shadow values, including the `none` option. + */ +export const getShadows = ( shadows = DEFAULT_PRESETS.settings.shadow.presets.map( preset => preset.shadow ) ) => { + return applyFilters( 'stackable.shadows', [ 'none', ...shadows ] ) +} + +/** + * Convert preset marks into values that Stackable can persist in attributes. + * + * A preset mark contains both its raw `shadow`, used for previews, and its + * `value`, normally a `--stk--preset` variable with the raw shadow as fallback. + * Values added through the legacy `stackable.shadows` filter remain raw CSS. + * + * @param {Array} shadowPresetMarks Presets returned by `usePresetControls`. + * @return {string[]} Attribute-ready shadow values. + */ +export const getGlobalShadowOptions = shadowPresetMarks => { + return getShadows( shadowPresetMarks.map( preset => preset.shadow ) ) + .map( shadowValue => { + return shadowPresetMarks.find( preset => preset.shadow === shadowValue )?.value || shadowValue + } ) +} + +/** + * Resolve the value consumed by the advanced shadow fields. + * + * The advanced fields split a raw CSS shadow into offsets, blur, spread, and + * color, so they cannot parse a preset's CSS variable expression directly. + * Custom shadows are already raw CSS and pass through unchanged. + * + * @param {number|string} selectedValue Selected preset index or `custom`. + * @param {Array} shadowPresets Normalized picker presets. + * @param {string} shadowFilterValue Stored raw or variable value. + * @return {string|undefined} Raw shadow when available, otherwise the stored value. + */ +export const getShadowFilterValue = ( selectedValue, shadowPresets, shadowFilterValue ) => { + return typeof selectedValue === 'number' + ? shadowPresets[ selectedValue ]?.shadow ?? shadowFilterValue + : shadowFilterValue } const FILTERS = [ @@ -255,23 +292,50 @@ const ShadowControl = memo( props => { ..._props } = props - const shadows = options || getShadows() + const shadowPresetMarks = usePresetControls( 'shadows' )?.getPresetMarks() || [] + // Normalize theme, user, built-in, and caller-supplied values so the picker + // can always render a label, raw preview, and persisted attribute value. + const defaultShadowPresets = [ { + name: __( 'No shadow', i18n ), + shadow: 'none', + value: 'none', + }, ...shadowPresetMarks ] + const shadowValues = options || getGlobalShadowOptions( shadowPresetMarks ) + const shadowPresets = shadowValues.map( ( shadowValue, index ) => { + const preset = defaultShadowPresets.find( item => item.value === shadowValue ) + return preset || { + name: index === 0 + ? __( 'No shadow', i18n ) + : sprintf( __( 'Shadow %d', i18n ), index ), + shadow: shadowValue, + value: shadowValue, + } + } ) const presetButtonRef = useRef( null ) const settingsButtonRef = useRef( null ) const [ openPopover, setOpenPopover ] = useState( '' ) + // Control handlers use a compact UI value: an index for presets, `custom` + // for unmatched CSS, and an empty string when no shadow is selected. + // Accepting both the variable value and raw CSS keeps existing attributes + // compatible when a site begins using global shadow presets. const valueCallback = value => { - return value ? shadows.indexOf( value ) === -1 ? 'custom' : shadows.indexOf( value ) : '' + if ( ! value ) { + return '' + } + const index = shadowPresets.findIndex( preset => preset.value === value || preset.shadow === value ) + return index === -1 ? 'custom' : index } const changeCallback = index => { - return index !== '' ? shadows[ index ] : index + return index !== '' ? shadowPresets[ index ]?.value : index } const [ _value, onChange ] = useControlHandlers( props.attribute, props.responsive, props.hover, valueCallback, changeCallback ) const value = typeof props.value === 'undefined' ? _value : props.value const effectiveOnChange = typeof props.onChange === 'undefined' ? onChange : props.onChange const selectedValue = value === '' ? valueCallback( props.placeholder ) : value + const hasTextPreview = props.previewType === 'text' const [ propsToPass, controlProps ] = extractControlProps( _props ) @@ -303,16 +367,25 @@ const ShadowControl = memo( props => { helpTooltip={ props.helpTooltip } hover={ props.hover } after={ ( - { openPopover === 'presets' && ( @@ -336,44 +409,56 @@ const ShadowControl = memo( props => {

{ __( 'Drop shadow', i18n ) }

- { shadows.map( ( shadow, index ) => { + { shadowPresets.map( ( preset, index ) => { const isSelected = selectedValue === index - const presetLabel = index === 0 - ? __( 'No shadow', i18n ) - : sprintf( __( 'Shadow %d', i18n ), index ) + const presetLabel = preset.name || sprintf( __( 'Shadow %d', i18n ), index ) return ( ) } ) }
- + { props.showClear && ( + + ) }
) } @@ -389,7 +474,7 @@ const ShadowControl = memo( props => { hasInset={ props.hasInset } isFilter={ props.isFilter } onEscape={ () => setOpenPopover( '' ) } - value={ props.shadowFilterValue } + value={ getShadowFilterValue( selectedValue, shadowPresets, props.shadowFilterValue ) } onChange={ props.shadowFilterOnChange } /> ) } @@ -406,6 +491,13 @@ ShadowControl.defaultProps = { changeCallback: null, isFilter: false, // If the style rule is `filter`, disable spread. hasInset: true, + previewType: 'box', + previewFontFamily: '', + allowReset: false, + showReset: null, + default: '', + onReset: undefined, + showClear: true, helpTooltip: { video: 'general-shadow', title: __( 'Shadow/Outline', i18n ), diff --git a/src/hooks/use-preset-controls.js b/src/hooks/use-preset-controls.js index 8bacdde9cb..efa256049b 100644 --- a/src/hooks/use-preset-controls.js +++ b/src/hooks/use-preset-controls.js @@ -1,4 +1,4 @@ -import { i18n } from 'stackable' +import { i18n, settings as stackableSettings } from 'stackable' import DEFAULT_PRESETS from '~stackable/plugins/global-settings/preset-controls/presets.json' import { useSettings } from '@wordpress/block-editor' import { useSelect } from '@wordpress/data' @@ -24,11 +24,19 @@ const PRESET_MAPPING = { prefix: 'block-height', }, borderRadius: { - settings: [ 'borderRadius' ], + settings: [ 'border', 'radiusSizes' ], defaultSizes: '', defaultEnabled: '', + useWordPressPresetsSetting: 'stackable_use_theme_border_radius_presets', prefix: 'border-radius', }, + shadows: { + settings: [ 'shadow', 'presets' ], + defaultSizes: 'shadow.presets.default', + defaultEnabled: 'shadow.defaultPresets', + prefix: 'shadow', + valueKey: 'shadow', + }, } const nonePreset = { @@ -38,6 +46,10 @@ const nonePreset = { } export const usePresetControls = property => { + const mapping = PRESET_MAPPING[ property ] + const useWordPressPresets = ! mapping.useWordPressPresetsSetting || + !! ( stackableSettings?.[ mapping.useWordPressPresetsSetting ] ?? true ) + // Get the theme presets for the property const [ _themePresets, @@ -54,9 +66,9 @@ export const usePresetControls = property => { wpDefaultPresets, defaultSizesEnabled, ] = useSettings( - PRESET_MAPPING[ property ].settings.join( '.' ), - PRESET_MAPPING[ property ].defaultSizes, - PRESET_MAPPING[ property ].defaultEnabled + mapping.settings.join( '.' ), + mapping.defaultSizes, + mapping.defaultEnabled ) // Get all custom presets @@ -65,7 +77,7 @@ export const usePresetControls = property => { return { allCustomPresets: { ..._customPresetControls } } }, [] ) - let themePresets = _themePresets + let themePresets = useWordPressPresets ? _themePresets : [] const hasThemePresets = Array.isArray( themePresets ) && themePresets.length > 0 // Merge theme presets with default presets when default sizes are enabled. @@ -74,14 +86,14 @@ export const usePresetControls = property => { if ( hasThemePresets && wpDefaultPresets && defaultSizesEnabled !== false ) { // Create a set for removing duplicates. const existingSlugs = new Set() - _themePresets.forEach( item => { + themePresets.forEach( item => { if ( item && typeof item.slug === 'string' ) { existingSlugs.add( item.slug ) } } ) themePresets = [ - ..._themePresets, + ...themePresets, ...wpDefaultPresets.filter( item => ! existingSlugs.has( item.slug ) ), ] } @@ -89,7 +101,7 @@ export const usePresetControls = property => { // Get the theme/default presets if the user have one, else return the stackable presets const basePresets = hasThemePresets ? themePresets - : PRESET_MAPPING[ property ].settings.reduce( ( acc, key ) => acc?.[ key ], DEFAULT_PRESETS.settings ) + : mapping.settings.reduce( ( acc, key ) => acc?.[ key ], DEFAULT_PRESETS.settings ) // Returns the base presets overriden by the custom presets const getMergedPresets = () => { @@ -112,7 +124,10 @@ export const usePresetControls = property => { // Setting customOnly to true returns the preset marks for custom presets only // Setting addNonePreset to true adds a none preset with a value of 0 const getPresetMarks = ( { customOnly = false, addNonePreset = false } = {} ) => { - const prefix = PRESET_MAPPING[ property ].prefix + const { + prefix, + valueKey = 'size', + } = PRESET_MAPPING[ property ] let presets = customOnly ? allCustomPresets[ property ] ?? [] : getMergedPresets() // Add the none preset presets = [ ...( addNonePreset ? [ nonePreset ] : [] ), ...presets ] @@ -121,7 +136,7 @@ export const usePresetControls = property => { .filter( preset => ! ( preset?.isDiscarded ) ) .map( preset => ( { ...preset, - value: `var(--stk--preset--${ prefix }--${ preset.slug }, ${ preset.size })`, + value: `var(--stk--preset--${ prefix }--${ preset.slug }, ${ preset[ valueKey ] })`, } ) ) } diff --git a/src/plugins/global-settings/preset-controls/editor-loader.js b/src/plugins/global-settings/preset-controls/editor-loader.js index 25042dff93..15db819591 100644 --- a/src/plugins/global-settings/preset-controls/editor-loader.js +++ b/src/plugins/global-settings/preset-controls/editor-loader.js @@ -22,16 +22,36 @@ const PRESET_MAPPING = { borderRadius: { prefix: 'border-radius', }, + shadows: { + prefix: 'shadow', + valueKey: 'shadow', + }, } +/** + * Render editor-only variables for custom presets. + * + * PHP emits the base preset variables for the frontend and editor. + * This style element reflects unsaved Global Design System changes immediately. + * + * @param {Object} customPresets Custom presets grouped by preset family. + * @param {Function} setStyles Updates the editor style element. + */ const renderGlobalStyles = ( customPresets, setStyles ) => { let css = '' Object.entries( customPresets ).forEach( ( [ key, presets ] ) => { + const mapping = PRESET_MAPPING[ key ] + if ( ! mapping || ! Array.isArray( presets ) ) { + return + } + const valueKey = mapping.valueKey || 'size' const styleRules = presets?.map( preset => { - return preset && ( ! preset?.isDiscarded ) - ? `--stk--preset--${ PRESET_MAPPING[ key ]?.prefix }--${ preset?.slug || '' }: ${ preset?.size || '' };` - : '' + if ( ! preset || preset.isDiscarded ) { + return '' + } + const presetValue = preset[ valueKey ] || '' + return '--stk--preset--' + mapping.prefix + '--' + ( preset.slug || '' ) + ': ' + presetValue + ';' } ) css += compact( styleRules ).join( '' ) } ) diff --git a/src/plugins/global-settings/preset-controls/index.php b/src/plugins/global-settings/preset-controls/index.php index 512f8d1438..b5b65adeb5 100644 --- a/src/plugins/global-settings/preset-controls/index.php +++ b/src/plugins/global-settings/preset-controls/index.php @@ -28,9 +28,15 @@ class Stackable_Size_And_Spacing_Preset_Controls { 'prefix' => 'block-height', ), 'borderRadius' => array( - 'settings' => array( 'borderRadius' ), + 'settings' => array( 'border', 'radiusSizes' ), + 'use_wordpress_presets_setting' => 'stackable_use_theme_border_radius_presets', 'prefix' => 'border-radius', ), + 'shadows' => array( + 'settings' => array( 'shadow', 'presets' ), + 'prefix' => 'shadow', + 'value_key' => 'shadow', + ), ); public $custom_presets; @@ -41,9 +47,12 @@ class Stackable_Size_And_Spacing_Preset_Controls { /** * Initialize */ - function __construct() { + function __construct() { add_action( 'register_stackable_global_settings', array( $this, 'register_use_size_presets_by_default' ) ); + add_action( 'register_stackable_global_settings', array( $this, 'register_use_theme_border_radius_presets' ) ); add_action( 'stackable_early_version_upgraded', array( $this, 'use_size_presets_by_default_set_default' ), 10, 2 ); + add_action( 'stackable_early_version_upgraded', array( $this, 'initialize_use_theme_border_radius_presets' ), 10, 2 ); + add_action( 'stackable_early_version_upgraded_frontend', array( $this, 'initialize_use_theme_border_radius_presets' ), 10, 2 ); add_action( 'stackable_early_version_upgraded', array( $this, 'migrate_global_typography_font_size' ), 10, 2 ); add_filter( 'stackable_js_settings', array( $this, 'add_setting' ) ); @@ -66,6 +75,37 @@ function register_use_size_presets_by_default() { ); } + // Register the setting for using border-radius presets from WordPress. + function register_use_theme_border_radius_presets() { + register_setting( + 'stackable_global_settings', + 'stackable_use_theme_border_radius_presets', + array( + 'type' => 'boolean', + 'description' => __( 'Use border-radius presets from the active theme and WordPress', STACKABLE_I18N ), + 'sanitize_callback' => 'rest_sanitize_boolean', + 'show_in_rest' => true, + 'default' => true, + ) + ); + } + + /** + * Keep bundled border-radius presets for upgrades while enabling theme + * presets on fresh installations. add_option() preserves later choices. + * + * @param string $old_version Previously installed Stackable version. + * @param string $new_version Newly installed Stackable version. + */ + public function initialize_use_theme_border_radius_presets( $old_version, $new_version ) { + add_option( + 'stackable_use_theme_border_radius_presets', + empty( $old_version ), + '', + false + ); + } + /** * When upgrading to v3.16.0 and above, set option to false. * If new installation, set option to true. @@ -117,6 +157,7 @@ public function migrate_global_typography_font_size( $old_version, $new_version // Make the setting available in the editor public function add_setting( $settings ) { $settings['stackable_use_size_presets_by_default'] = get_option( 'stackable_use_size_presets_by_default' ); + $settings['stackable_use_theme_border_radius_presets'] = (bool) get_option( 'stackable_use_theme_border_radius_presets', true ); return $settings; } @@ -153,16 +194,20 @@ private function load_json_file( $json_path ) { } /** - * Generate CSS variable style definitions based on the property (e.g., fontSizes, spacing). - * The given presets will be overriden it match with a preset from custom. - * - * @param array $property - * @param array $presets - * @param array $prefix - * @param bool $isTheme - * @return mixed + * Generate Stackable CSS variables for one preset family. + * + * Custom presets override base presets with the same slug. + * WordPress presets point to their generated `--wp--preset` variables so + * theme.json behavior remains intact, while Stackable presets use raw values. + * + * @param string $property Stackable preset family key. + * @param array $presets Base presets for the selected origin. + * @param string $prefix CSS variable preset prefix. + * @param bool $isTheme Whether the presets came from WordPress. + * @param string $value_key Preset field containing the raw CSS value. + * @return array Style Engine rule containing the generated declarations. */ - public function generate_css_variables_styles( $property, $presets, $prefix, $isTheme = false ) { + public function generate_css_variables_styles( $property, $presets, $prefix, $isTheme = false, $value_key = 'size' ) { $filter_name = current_filter(); $custom_presets = $this->custom_presets[ $property ] ?? []; @@ -193,7 +238,7 @@ public function generate_css_variables_styles( $property, $presets, $prefix, $is $is_custom = $preset['__is_custom'] ?? false; $value = $is_custom || ! $isTheme - ? $preset['size'] + ? ( $preset[ $value_key ] ?? '' ) : "var(--wp--preset--$prefix--$slug)"; $css_vars[ "--stk--preset--$prefix--$slug" ] = $value; @@ -228,31 +273,42 @@ public function add_preset_controls_styles( $current_css ) { $generated_styles = array(); foreach ( self::PRESET_MAPPING as $key => $value ) { - if ( ! empty( $this->deepGet( $this->theme_presets, $value[ 'settings' ] )[ 'theme' ] ) ) { + $value_key = $value[ 'value_key' ] ?? 'size'; + $use_wordpress_presets = ! isset( $value[ 'use_wordpress_presets_setting' ] ) || + (bool) get_option( $value[ 'use_wordpress_presets_setting' ], true ); + $theme_presets = $use_wordpress_presets + ? ( $this->deepGet( $this->theme_presets, $value[ 'settings' ] )[ 'theme' ] ?? array() ) + : array(); + $default_presets = $use_wordpress_presets + ? ( $this->deepGet( $this->default_presets, $value[ 'settings' ] )[ 'default' ] ?? array() ) + : array(); + + if ( ! empty( $theme_presets ) ) { $styles = $this->generate_css_variables_styles( $key, - $this->deepGet( $this->theme_presets, $value[ 'settings' ] )[ 'theme' ], + $theme_presets, $value[ 'prefix' ], - true + true, + $value_key ); - $generated_styles[] = $styles; - - } elseif ( ! empty( $this->deepGet( $this->default_presets, $value[ 'settings' ] )[ 'default' ] ) ) { + } elseif ( ! empty( $default_presets ) ) { $styles = $this->generate_css_variables_styles( $key, - $this->deepGet( $this->default_presets, $value[ 'settings' ] )[ 'default' ], + $default_presets, $value[ 'prefix' ], - true + true, + $value_key ); - $generated_styles[] = $styles; } else { $styles = $this->generate_css_variables_styles( $key, - $this->deepGet( $this->stackable_presets, $value[ 'settings' ] ), + $this->deepGet( $this->stackable_presets, $value[ 'settings' ] ), $value[ 'prefix' ], + false, + $value_key ); - $generated_styles[] = $styles; } + $generated_styles[] = $styles; } $generated_css = wp_style_engine_get_stylesheet_from_css_rules( $generated_styles ); @@ -268,4 +324,4 @@ public function add_preset_controls_styles( $current_css ) { } new Stackable_Size_And_Spacing_Preset_Controls(); -} \ No newline at end of file +} diff --git a/src/plugins/global-settings/preset-controls/presets.json b/src/plugins/global-settings/preset-controls/presets.json index df2612f86e..f42fab638b 100644 --- a/src/plugins/global-settings/preset-controls/presets.json +++ b/src/plugins/global-settings/preset-controls/presets.json @@ -135,42 +135,93 @@ "slug": "full" } ], - "borderRadius": [ - { - "name": "XS", - "size": "2px", - "slug": "x-small" - }, - { - "name": "S", - "size": "4px", - "slug": "small" - }, - { - "name": "M", - "size": "8px", - "slug": "medium" - }, - { - "name": "L", - "size": "16px", - "slug": "large" - }, - { - "name": "XL", - "size": "24px", - "slug": "x-large" - }, - { - "name": "2XL", - "size": "32px", - "slug": "xx-large" - }, - { - "name": "Full", - "size": "9999px", - "slug": "full" - } - ] + "border": { + "radiusSizes": [ + { + "name": "XS", + "size": "2px", + "slug": "x-small" + }, + { + "name": "S", + "size": "4px", + "slug": "small" + }, + { + "name": "M", + "size": "8px", + "slug": "medium" + }, + { + "name": "L", + "size": "16px", + "slug": "large" + }, + { + "name": "XL", + "size": "24px", + "slug": "x-large" + }, + { + "name": "2XL", + "size": "32px", + "slug": "xx-large" + }, + { + "name": "Full", + "size": "9999px", + "slug": "full" + } + ] + }, + "shadow": { + "presets": [ + { + "name": "Shadow 1", + "slug": "shadow-1", + "shadow": "0 0 0 1px #7878781a" + }, + { + "name": "Shadow 2", + "slug": "shadow-2", + "shadow": "0 0 0 2px #7878781a" + }, + { + "name": "Shadow 3", + "slug": "shadow-3", + "shadow": "0 5px 5px 0 #123f5209" + }, + { + "name": "Shadow 4", + "slug": "shadow-4", + "shadow": "0px 2px 20px #99999933" + }, + { + "name": "Shadow 5", + "slug": "shadow-5", + "shadow": "0 5px 30px -10px #123f524d" + }, + { + "name": "Shadow 6", + "slug": "shadow-6", + "shadow": "0px 10px 30px #0000000d" + }, + { + "name": "Shadow 7", + "slug": "shadow-7", + "shadow": "7px 5px 30px #48497927" + }, + { + "name": "Shadow 8", + "slug": "shadow-8", + "shadow": "0px 10px 60px #0000001a" + }, + { + "name": "Shadow 9", + "slug": "shadow-9", + "shadow": "0px 70px 90px -20px #4849794d" + } + ] + } } } diff --git a/src/plugins/global-settings/utils/use-block-layout-inspector-utils.js b/src/plugins/global-settings/utils/use-block-layout-inspector-utils.js index 17adaa9033..2c3b93ba60 100644 --- a/src/plugins/global-settings/utils/use-block-layout-inspector-utils.js +++ b/src/plugins/global-settings/utils/use-block-layout-inspector-utils.js @@ -6,9 +6,11 @@ import { hoverState } from './block-layout-utils' /** * External dependencies */ -import { getShadows } from '~stackable/components' +import { getGlobalShadowOptions } from '~stackable/components' import { IMAGE_SHADOWS } from '~stackable/block-components' -import { useDeviceType, useBlockHoverState } from '~stackable/hooks' +import { + useDeviceType, useBlockHoverState, usePresetControls, +} from '~stackable/hooks' /** * WordPress dependencies @@ -26,7 +28,8 @@ export const useBlockLayoutInspectorUtils = ( storeName, optionName, setDisplayH const [ currentHoverState ] = useBlockHoverState( { forceUpdateHoverState: true } ) const deviceType = useDeviceType() - const shadows = getShadows() + const shadowPresetMarks = usePresetControls( 'shadows' )?.getPresetMarks() || [] + const shadows = getGlobalShadowOptions( shadowPresetMarks ) const getValue = ( property, { responsive = false, hover = false, unit = false, @@ -41,7 +44,23 @@ export const useBlockLayoutInspectorUtils = ( storeName, optionName, setDisplayH const valueCallback = ( value, isImage = false ) => { const options = isImage ? IMAGE_SHADOWS : shadows - return value ? ( options.indexOf( value ) === -1 ? 'custom' : options.indexOf( value ) ) : '' + if ( ! value ) { + return '' + } + const index = options.indexOf( value ) + if ( index !== -1 ) { + return index + } + if ( ! isImage ) { + // Older attributes may store raw CSS while current global presets use + // a Stackable variable. Match the raw shadow before marking it custom. + const rawPreset = shadowPresetMarks.find( preset => preset.shadow === value ) + const presetIndex = rawPreset ? options.indexOf( rawPreset.value ) : -1 + if ( presetIndex !== -1 ) { + return presetIndex + } + } + return 'custom' } const changeCallback = ( index, isImage = false ) => { diff --git a/src/welcome/admin.js b/src/welcome/admin.js index b621d886b6..5f042828a4 100644 --- a/src/welcome/admin.js +++ b/src/welcome/admin.js @@ -1554,6 +1554,16 @@ const AdditionalOptions = props => { } } __nextHasNoMarginBottom /> + { + handleSettingsChange( { stackable_use_theme_border_radius_presets: checked } ) // eslint-disable-line camelcase + } } + __nextHasNoMarginBottom + /> Date: Wed, 30 Sep 2026 10:22:21 +0800 Subject: [PATCH 03/10] fix: bigger typography shadow control --- src/components/shadow-control/editor.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/shadow-control/editor.scss b/src/components/shadow-control/editor.scss index a0303b8c12..52f68774f5 100644 --- a/src/components/shadow-control/editor.scss +++ b/src/components/shadow-control/editor.scss @@ -69,7 +69,7 @@ .stk-shadow-control__preset-text { color: #2f2f2f; - font-size: 18px; + font-size: 22px; line-height: 1; white-space: nowrap; } From bc8f46321d52ac9a825f5a3da6deef4086013ed2 Mon Sep 17 00:00:00 2001 From: Arukuen Date: Mon, 12 Oct 2026 10:46:03 +0800 Subject: [PATCH 04/10] fix: finalize extended inheritance with column spacing and gap default adjustments --- src/block-components/columns/edit.js | 4 +- src/block/columns/style.scss | 2 +- src/hooks/use-global-block-layout-defaults.js | 2 + .../spacing-and-borders/index.js | 6 +- .../theme-block-style-inheritance/index.php | 247 +++++++++++++++++- src/welcome/admin.js | 15 +- 6 files changed, 261 insertions(+), 15 deletions(-) diff --git a/src/block-components/columns/edit.js b/src/block-components/columns/edit.js index 2646598fdd..341ec57016 100644 --- a/src/block-components/columns/edit.js +++ b/src/block-components/columns/edit.js @@ -259,7 +259,7 @@ export const Controls = props => { defaultLocked={ true } min={ [ 0, 0 ] } sliderMax={ [ 200, 30 ] } - placeholder={ numInnerBlocks === 1 ? '0' : getPlaceholder( 'column-margin' ) } + placeholder={ numInnerBlocks === 1 ? '0' : getPlaceholder( 'column-margin', { device: deviceType.toLowerCase() } ) } visualGuide={ { selector: '.stk-%s-column > * > * > [data-type="stackable/column"] > * > .stk-column > .stk-inner-blocks', highlight: 'column-spacing', @@ -278,7 +278,7 @@ export const Controls = props => { responsive="all" min={ 0 } sliderMax={ 100 } - placeholder={ getPlaceholder( 'columns-column-gap' ) } + placeholder={ getPlaceholder( 'columns-column-gap', { device: deviceType.toLowerCase() } ) } visualGuide={ { selector: '.stk-%s-column > * > *', highlight: 'columns:column-gap', diff --git a/src/block/columns/style.scss b/src/block/columns/style.scss index 148cf2fd50..decb7bf6c5 100644 --- a/src/block/columns/style.scss +++ b/src/block/columns/style.scss @@ -10,7 +10,7 @@ } .stk-block-columns > .stk-block-content { - --stk-column-gap: 0px; // For nested columns, this takes precedence. + --stk-column-gap: var(--stk-columns-column-gap, 0px); // For nested columns, this takes precedence. column-gap: var(--stk-column-gap); margin-left: auto; margin-right: auto; diff --git a/src/hooks/use-global-block-layout-defaults.js b/src/hooks/use-global-block-layout-defaults.js index 71eb1565ac..28163e380c 100644 --- a/src/hooks/use-global-block-layout-defaults.js +++ b/src/hooks/use-global-block-layout-defaults.js @@ -1,5 +1,6 @@ import { useSelect } from '@wordpress/data' import rawBlockDesignSystem from '~stackable/styles/block-design-system.json' +import { settings as stackableSettings } from 'stackable' /** * Provides a function to get the placeholder for block componets from block layout settings. * @@ -22,6 +23,7 @@ export const useBlockLayoutDefaults = () => { } ) return blockDesignSystem }, {} ) + Object.assign( defaults, stackableSettings.stackable_column_spacing_defaults || {} ) const getInheritedValue = ( obj, property, device ) => { let value = obj[ property ]?.[ device ] diff --git a/src/plugins/global-settings/spacing-and-borders/index.js b/src/plugins/global-settings/spacing-and-borders/index.js index ca071063e7..9252e67bd5 100644 --- a/src/plugins/global-settings/spacing-and-borders/index.js +++ b/src/plugins/global-settings/spacing-and-borders/index.js @@ -63,6 +63,8 @@ addFilter( 'stackable.global-settings.inspector', 'stackable/global-spacing-and- ?.getPresetMarks( { addNonePreset: true } ) || null const borderRadiusPresetMarks = usePresetControls( 'borderRadius' ) ?.getPresetMarks( { addNonePreset: true } ) || null + const deviceType = useDeviceType().toLowerCase() + const { getPlaceholder } = useBlockLayoutDefaults() return ( <> @@ -142,7 +144,7 @@ addFilter( 'stackable.global-settings.inspector', 'stackable/global-spacing-and- responsive="all" min={ [ 0, 0 ] } sliderMax={ [ 200, 30 ] } - placeholder="12" + placeholder={ getPlaceholder( 'column-margin', { device: deviceType } ) } units={ [ 'px', 'em', 'vw' ] } unit={ getValue( 'column-margin', STATES.RESPONSIVE_UNIT ) || 'px' } onChangeUnit={ value => onChange( 'column-margin', value, STATES.RESPONSIVE_UNIT ) } @@ -160,7 +162,7 @@ addFilter( 'stackable.global-settings.inspector', 'stackable/global-spacing-and- responsive="all" min={ 0 } sliderMax={ 100 } - placeholder="0" + placeholder={ getPlaceholder( 'columns-column-gap', { device: deviceType } ) } value={ getValue( 'columns-column-gap', STATES.RESPONSIVE ) } onChange={ value => onChange( 'columns-column-gap', value, STATES.RESPONSIVE ) } hasTabletValue={ getHasDeviceValue( 'columns-column-gap', 'tablet' ) } diff --git a/src/plugins/theme-block-style-inheritance/index.php b/src/plugins/theme-block-style-inheritance/index.php index 00a017a10c..0fa5523827 100644 --- a/src/plugins/theme-block-style-inheritance/index.php +++ b/src/plugins/theme-block-style-inheritance/index.php @@ -11,6 +11,17 @@ * Stackable Block Style Inheritance */ class Stackable_Block_Style_Inheritance { + /** Gap-based Columns defaults enabled by extended inheritance. */ + const UPDATED_COLUMN_DEFAULTS = array( + 'column-margin' => array( + 'desktop' => 0, + 'mobile' => 0, + ), + 'columns-column-gap' => array( + 'desktop' => 24, + 'mobile' => 16, + ), + ); /** * Initialize @@ -18,6 +29,8 @@ class Stackable_Block_Style_Inheritance { function __construct() { add_action( 'register_stackable_global_settings', array( $this, 'register_block_style_inheritance' ) ); add_action( 'stackable_early_version_upgraded', array( $this, 'block_style_inheritance_set_default' ), 10, 2 ); + add_action( 'stackable_early_version_upgraded', array( $this, 'initialize_extended_block_style_inheritance' ), 10, 2 ); + add_action( 'stackable_early_version_upgraded_frontend', array( $this, 'initialize_extended_block_style_inheritance' ), 10, 2 ); add_action( 'body_class', array( $this, 'add_body_class_block_style_inheritance' ) ); add_filter( 'stackable_block_style_inheritance_inline_styles_nodep', array( $this, 'add_block_style_inheritance' ) ); @@ -40,6 +53,36 @@ function register_block_style_inheritance() { 'default' => '', ) ); + + // Keep new structural mappings separate from legacy inheritance for compatibility. + register_setting( + 'stackable_global_settings', + 'stackable_enable_extended_block_style_inheritance', + array( + 'type' => 'boolean', + 'description' => __( 'Use updated Columns spacing and inherit structural styles from equivalent WordPress blocks', STACKABLE_I18N ), + 'sanitize_callback' => 'rest_sanitize_boolean', + 'show_in_rest' => true, + 'default' => true, + ) + ); + } + + /** + * Preserve existing sites while enabling expanded mappings on fresh installs. + * + * add_option() does not overwrite a choice already made by the user. + * + * @param string $old_version Previously installed Stackable version. + * @param string $new_version Newly installed Stackable version. + */ + function initialize_extended_block_style_inheritance( $old_version, $new_version ) { + add_option( + 'stackable_enable_extended_block_style_inheritance', + empty( $old_version ), + '', + false + ); } /** @@ -64,10 +107,14 @@ function block_style_inheritance_set_default( $old_version, $new_version ) { */ function add_block_style_inheritance( $current_css ) { $disable_block_style_inheritance = get_option( 'stackable_disable_block_style_inheritance' ); + $enable_extended_block_style_inheritance = (bool) get_option( 'stackable_enable_extended_block_style_inheritance', true ); + $extended_column_default_rules = $enable_extended_block_style_inheritance + ? $this->get_extended_column_default_rules() + : array(); - // Do not generate styles if it is disabled in the settings. + // Updated Columns defaults are controlled independently from theme.json inheritance. if ( $disable_block_style_inheritance ) { - return $current_css; + return $this->add_rules_to_stylesheet( $current_css, $extended_column_default_rules ); } // Grab all the styles defined in the current theme.json @@ -78,9 +125,9 @@ function add_block_style_inheritance( $current_css ) { $styles_data->merge( WP_Theme_JSON_Resolver::get_user_data() ); $raw_data = $styles_data->get_raw_data(); - // Don't generate styles if it's not a block theme/there's no theme.json + // Without theme.json styles, only the independently controlled Columns defaults apply. if ( ! isset( $raw_data[ 'styles' ] ) ) { - return; + return $this->add_rules_to_stylesheet( $current_css, $extended_column_default_rules ); } $styles = $raw_data['styles']; @@ -89,8 +136,8 @@ function add_block_style_inheritance( $current_css ) { $styles = wp_get_global_styles(); } - // Caching since this can be expensive. - $hash = md5( serialize( $styles ) ); + // Include the feature flag so toggling it invalidates the cached stylesheet. + $hash = md5( serialize( array( $styles, $enable_extended_block_style_inheritance ) ) ); if ( $hash === get_transient( 'stackable_block_style_inheritance_hash' ) ) { $generated_css = get_transient( 'stackable_block_style_inheritance_css' ); if ( $generated_css ) { @@ -101,7 +148,7 @@ function add_block_style_inheritance( $current_css ) { // This consists of the selectors and declarations // to be used in wp_style_engine_get_stylesheet_from_css_rules - $style_declarations = array( + $style_declarations = array_merge( $extended_column_default_rules, array( 'root' => array( 'selector' => ':root', 'declarations' => array(), @@ -170,7 +217,7 @@ function add_block_style_inheritance( $current_css ) { 'selector' => ':where(.stk-block-heading__text, .stk-block-text__text, .stk-block-table-of-contents__link-wrapper, .stk-block-subtitle__text, .is-style-link) a', 'declarations' => array(), ), - ); + ) ); /** These properties will be added to the `:root` selector * Dev note: @@ -192,6 +239,24 @@ function add_block_style_inheritance( $current_css ) { $style_declarations['root']['declarations'][ '--stk-button-row-gap' ] = $root_properties[ 'button-group-gap' ] ?? $root_properties[ 'default-gap' ]; } + // Extended mode replaces the legacy declarations with axis-aware values. + if ( $enable_extended_block_style_inheritance ) { + $button_group_gap = $root_properties[ 'button-group-gap' ]; + if ( null === $button_group_gap || '' === $button_group_gap ) { + $button_group_gap = $root_properties[ 'default-gap' ]; + } + + // Remove both legacy declarations so a missing axis cannot retain the raw value. + unset( + $style_declarations['root']['declarations'][ '--stk-button-column-gap' ], + $style_declarations['root']['declarations'][ '--stk-button-row-gap' ] + ); + + foreach ( $this->normalize_block_gap_axes( $button_group_gap ) as $axis => $value ) { + $style_declarations['root']['declarations'][ "--stk-button-{$axis}-gap" ] = $value; + } + } + // DEV NOTE: Remove column gap from inheritance since it breaks our design library. // if ( $root_properties[ 'columns-gap' ] || $root_properties[ 'default-gap' ] ) { // $style_declarations['root']['declarations'][ '--stk-columns-column-gap' ] = $root_properties[ 'columns-gap' ] ?? $root_properties[ 'default-gap' ]; @@ -290,6 +355,20 @@ function add_block_style_inheritance( $current_css ) { 'h6' => array( 'keys' => array( 'elements', 'h6' ) ), ); + // Register extended declarations only when the compatibility setting is enabled. + if ( $enable_extended_block_style_inheritance ) { + $extended_element_settings = $this->get_extended_element_settings(); + + foreach ( $extended_element_settings as $element => $settings ) { + $style_declarations[ $element ] = array( + 'selector' => $settings[ 'selector' ], + 'declarations' => array(), + ); + } + + $element_settings = array_merge( $element_settings, $extended_element_settings ); + } + foreach ($element_settings as $element => $settings ) { $keys = array( 'normal' => $settings[ 'keys' ] ); if ( isset( $settings[ 'hover-keys' ] ) ) { @@ -302,6 +381,16 @@ function add_block_style_inheritance( $current_css ) { // add style declarations $style_declarations = $this->get_declarations( $styles, $style_declarations, $selector, $key ); + // wp_style_engine_get_styles() does not parse spacing.blockGap, so map it + // manually to the Stackable properties declared by this element setting. + if ( isset( $settings[ 'block_gap_properties' ] ) ) { + $block_gap = $this->get_value( $styles, array_merge( $key, array( 'spacing', 'blockGap' ) ) ); + foreach ( $this->normalize_block_gap_axes( $block_gap ) as $axis => $value ) { + $property = $settings[ 'block_gap_properties' ][ $axis ]; + $style_declarations[ $selector ][ 'declarations' ][ $property ] = $value; + } + } + // remove properties from style declarations if ( isset( $settings[ 'remove_properties' ] ) ) { $style_declarations = $this->remove_properties( $style_declarations, $settings[ 'remove_properties' ], $selector ); @@ -376,6 +465,142 @@ function allow_css( $allow_css, $css_test_string ) { return $allow_css; } + /** + * Return the compatibility-gated mappings between Core and Stackable blocks. + * + * Structural properties intentionally target the Stackable layer that most + * closely matches the corresponding Core block selector. + * + * @return array Extended block style mappings. + */ + private function get_extended_element_settings() { + $columns_block_gap_properties = array( + 'column' => '--stk-columns-column-gap', + 'row' => 'row-gap', + ); + + return array( + 'extended-image' => array( + 'selector' => ':where(.stk-block-image) .stk-img-wrapper img', + 'keys' => array( 'blocks', 'core/image' ), + 'get_properties' => array( + 'border-radius', + 'border-top-left-radius', + 'border-top-right-radius', + 'border-bottom-left-radius', + 'border-bottom-right-radius', + ), + ), + 'extended-columns-gap' => array( + 'selector' => ':where(.stk-block-columns) > .stk-block-content', + 'keys' => array( 'blocks', 'core/columns' ), + 'get_properties' => array( '--stk-columns-column-gap', 'row-gap' ), + 'block_gap_properties' => $columns_block_gap_properties, + ), + // The editor adds wrappers between the block and the element that owns the gap. + 'extended-columns-gap-editor' => array( + 'selector' => ':where(.stk-block-columns) > .stk-block-content > .block-editor-inner-blocks > .block-editor-block-list__layout', + 'keys' => array( 'blocks', 'core/columns' ), + 'get_properties' => array( '--stk-columns-column-gap', 'row-gap' ), + 'block_gap_properties' => $columns_block_gap_properties, + ), + ); + } + + /** + * Return the updated Columns defaults as ordinary Style Engine rules. + * + * The compiled design system retains the legacy defaults. These rules opt + * fresh installations, and existing installations that enable the feature, + * into the gap-based Columns model. + * + * @return array Updated Columns default rules. + */ + private function get_extended_column_default_rules() { + return array( + 'extended-column-defaults' => array( + 'selector' => ':root', + 'declarations' => array( + '--stk-column-margin' => self::UPDATED_COLUMN_DEFAULTS[ 'column-margin' ][ 'desktop' ] . 'px', + '--stk-columns-column-gap' => self::UPDATED_COLUMN_DEFAULTS[ 'columns-column-gap' ][ 'desktop' ] . 'px', + ), + ), + 'extended-column-defaults-mobile' => array( + 'rules_group' => '@media (max-width:767px)', + 'selector' => ':root', + 'declarations' => array( + '--stk-column-margin' => self::UPDATED_COLUMN_DEFAULTS[ 'column-margin' ][ 'mobile' ] . 'px', + '--stk-columns-column-gap' => self::UPDATED_COLUMN_DEFAULTS[ 'columns-column-gap' ][ 'mobile' ] . 'px', + ), + ), + ); + } + + /** + * Append Style Engine rules to an existing stylesheet. + * + * @param string $current_css Existing CSS. + * @param array $rules Style Engine rules keyed by internal name. + * @return string CSS with the generated rules appended. + */ + private function add_rules_to_stylesheet( $current_css, $rules ) { + if ( empty( $rules ) ) { + return $current_css; + } + + $generated_css = wp_style_engine_get_stylesheet_from_css_rules( array_values( $rules ) ); + return $generated_css ? $current_css . $generated_css : $current_css; + } + + /** + * Convert theme.json block spacing into Stackable's row and column gap values. + * + * WordPress stores linked block spacing as a scalar and independently set + * vertical and horizontal spacing as top and left values. + * + * @param mixed $block_gap theme.json blockGap value. + * @return array Valid row and column gap values. + */ + private function normalize_block_gap_axes( $block_gap ) { + $values = is_array( $block_gap ) + ? array( + 'column' => $block_gap[ 'left' ] ?? null, + 'row' => $block_gap[ 'top' ] ?? null, + ) + : array( + 'column' => $block_gap, + 'row' => $block_gap, + ); + + return array_map( + array( $this, 'normalize_style_value' ), + array_filter( + $values, + function( $value ) { + return is_string( $value ) || is_numeric( $value ); + } + ) + ); + } + + /** + * Resolve internal theme.json variables through the WordPress Style Engine. + * + * @param string|int|float $value Raw style value. + * @return string|int|float Resolved CSS value. + */ + private function normalize_style_value( $value ) { + $resolved = wp_style_engine_get_styles( + array( + 'spacing' => array( + 'padding' => array( 'top' => $value ), + ), + ) + ); + + return $resolved[ 'declarations' ][ 'padding-top' ] ?? $value; + } + /** * Helper function to safely traverse a nested associative array using an array of keys. * Returns null if any key in the path doesn't exist. @@ -543,6 +768,12 @@ function get_declarations( $styles, $style_declarations, $element, $element_keys public function add_setting( $settings ) { $settings['stackable_disable_block_style_inheritance'] = get_option( 'stackable_disable_block_style_inheritance', false ); + $enable_extended_block_style_inheritance = (bool) get_option( 'stackable_enable_extended_block_style_inheritance', true ); + $settings['stackable_enable_extended_block_style_inheritance'] = $enable_extended_block_style_inheritance; + // Override editor placeholders only when the updated Columns defaults are active. + $settings['stackable_column_spacing_defaults'] = $enable_extended_block_style_inheritance + ? self::UPDATED_COLUMN_DEFAULTS + : array(); return $settings; } } diff --git a/src/welcome/admin.js b/src/welcome/admin.js index 5f042828a4..a68b5c623c 100644 --- a/src/welcome/admin.js +++ b/src/welcome/admin.js @@ -218,6 +218,7 @@ const SEARCH_TREE = [ __( 'Show Go premium notices', i18n ), __( 'Generate Global Colors for native blocks', i18n ), __( 'Inherit Block Styles from theme.json', i18n ), + __( 'Enable Extended Block Style Inheritance', i18n ), __( 'Use v3.16.0 Color Scheme Inheritance', i18n ), __( 'Block Defaults (Sunsetted)', i18n ), ], @@ -1521,8 +1522,8 @@ const AdditionalOptions = props => { <> { ( miscellaneous.children === null || miscellaneous.children.length > 0 ) &&
-

{ __( 'Miscellaneous', i18n ) }

-

{ __( 'Below are other minor settings. Some may be useful when upgrading from older versions of Stackable.', i18n ) }

+

{ __( 'Feature Flags', i18n ) }

+

{ __( "As Stackable gets fixes and updates, functionality and behavior change in the plugin. The options here are in order to retain the old behavior across updates. You usually won't have to adjust these options.", i18n ) }

{ showProNoticesOption && { } } __nextHasNoMarginBottom /> + { + handleSettingsChange( { stackable_enable_extended_block_style_inheritance: checked } ) // eslint-disable-line camelcase + } } + __nextHasNoMarginBottom + /> Date: Mon, 12 Oct 2026 11:21:33 +0800 Subject: [PATCH 05/10] fix: update e2e for changing name to Feature Flags --- e2e/tests/admin.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/e2e/tests/admin.spec.ts b/e2e/tests/admin.spec.ts index fc05779791..89b7b161e6 100644 --- a/e2e/tests/admin.spec.ts +++ b/e2e/tests/admin.spec.ts @@ -93,7 +93,8 @@ test( 'Settings inner tabs render', async ( { await expect( page.getByText( 'Force Typography Styles' ) ).toBeVisible() await page.getByRole( 'tab', { name: /Miscellaneous/ } ).click() - await expect( page.getByRole( 'heading', { name: 'Miscellaneous' } ) ).toBeVisible() + await expect( page.getByRole( 'heading', { name: 'Feature Flags' } ) ).toBeVisible() + await expect( page.getByRole( 'checkbox', { name: 'Enable Extended Block Style Inheritance' } ) ).toBeVisible() await expect( page.getByRole( 'checkbox', { name: 'Use Border Radius Presets from theme.json' } ) ).toBeVisible() await page.getByRole( 'tab', { name: 'Import/Export' } ).click() From 8646510e372d51299ac40ca4cfddd9c5673f430c Mon Sep 17 00:00:00 2001 From: Arukuen Date: Mon, 12 Oct 2026 13:55:24 +0800 Subject: [PATCH 06/10] fix: scope updated column defaults to Stackable Columns --- src/block-components/columns/edit.js | 2 +- src/block/column/edit.js | 7 +++++-- src/block/columns/edit.js | 4 ++-- src/hooks/use-global-block-layout-defaults.js | 6 +++--- src/plugins/theme-block-style-inheritance/index.php | 12 ++++++------ 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/block-components/columns/edit.js b/src/block-components/columns/edit.js index 341ec57016..da1189309c 100644 --- a/src/block-components/columns/edit.js +++ b/src/block-components/columns/edit.js @@ -41,7 +41,7 @@ export const Controls = props => { // TODO: Get global default value for placeholder const [ , setColumnsUpdate ] = useState( 0 ) const deviceType = useDeviceType() - const { getPlaceholder } = useBlockLayoutDefaults() + const { getPlaceholder } = useBlockLayoutDefaults( props.defaultOverrides ) const { clientId } = useBlockEditContext() const { numInnerBlocks, innerBlocks, diff --git a/src/block/column/edit.js b/src/block/column/edit.js index c82c9ae5e1..bdc5fbb351 100644 --- a/src/block/column/edit.js +++ b/src/block/column/edit.js @@ -7,7 +7,9 @@ import blockStyles from './style' * External dependencies */ import classnames from 'classnames' -import { i18n, version as VERSION } from 'stackable' +import { + i18n, settings, version as VERSION, +} from 'stackable' import { useBlockLayoutDefaults, usePresetControls } from '~stackable/hooks' import { AdvancedToggleControl, @@ -123,6 +125,7 @@ const Edit = props => { @@ -164,7 +167,7 @@ const Edit = props => { // Inspector controls for the block, it's important that we only pass only the // props used by controls to prevent rerenders of all the inspector controls. const InspectorControls = memo( props => { - const { getPlaceholder } = useBlockLayoutDefaults() + const { getPlaceholder } = useBlockLayoutDefaults( props.defaultOverrides ) const presets = usePresetControls( 'spacingSizes' ) ?.getPresetMarks( { addNonePreset: true } ) || null diff --git a/src/block/columns/edit.js b/src/block/columns/edit.js index 9c4ba33622..2f4c9e88ce 100644 --- a/src/block/columns/edit.js +++ b/src/block/columns/edit.js @@ -6,7 +6,7 @@ import blockStyles from './style' /** * External dependencies */ -import { version as VERSION } from 'stackable' +import { settings, version as VERSION } from 'stackable' import classnames from 'classnames' import { ColumnInnerBlocks, @@ -173,7 +173,7 @@ const InspectorControls = memo( () => { <> - + diff --git a/src/hooks/use-global-block-layout-defaults.js b/src/hooks/use-global-block-layout-defaults.js index 28163e380c..0172c34d1e 100644 --- a/src/hooks/use-global-block-layout-defaults.js +++ b/src/hooks/use-global-block-layout-defaults.js @@ -1,12 +1,12 @@ import { useSelect } from '@wordpress/data' import rawBlockDesignSystem from '~stackable/styles/block-design-system.json' -import { settings as stackableSettings } from 'stackable' /** * Provides a function to get the placeholder for block componets from block layout settings. * + * @param {Object} defaultOverrides Block-specific default values. * @return {Object} Functions */ -export const useBlockLayoutDefaults = () => { +export const useBlockLayoutDefaults = ( defaultOverrides = {} ) => { const { blockLayouts } = useSelect( select => { const spacingAndBorders = select( 'stackable/global-spacing-and-borders' ).getBlockLayouts() const buttonsAndIcons = select( 'stackable/global-buttons-and-icons' ).getBlockLayouts() @@ -23,7 +23,7 @@ export const useBlockLayoutDefaults = () => { } ) return blockDesignSystem }, {} ) - Object.assign( defaults, stackableSettings.stackable_column_spacing_defaults || {} ) + Object.assign( defaults, defaultOverrides ) const getInheritedValue = ( obj, property, device ) => { let value = obj[ property ]?.[ device ] diff --git a/src/plugins/theme-block-style-inheritance/index.php b/src/plugins/theme-block-style-inheritance/index.php index 0fa5523827..0f062e49ad 100644 --- a/src/plugins/theme-block-style-inheritance/index.php +++ b/src/plugins/theme-block-style-inheritance/index.php @@ -492,14 +492,14 @@ private function get_extended_element_settings() { ), ), 'extended-columns-gap' => array( - 'selector' => ':where(.stk-block-columns) > .stk-block-content', + 'selector' => ':where(.wp-block-stackable-columns.stk-block-columns) > .stk-block-content', 'keys' => array( 'blocks', 'core/columns' ), 'get_properties' => array( '--stk-columns-column-gap', 'row-gap' ), 'block_gap_properties' => $columns_block_gap_properties, ), // The editor adds wrappers between the block and the element that owns the gap. 'extended-columns-gap-editor' => array( - 'selector' => ':where(.stk-block-columns) > .stk-block-content > .block-editor-inner-blocks > .block-editor-block-list__layout', + 'selector' => ':where(.wp-block-stackable-columns.stk-block-columns) > .stk-block-content > .block-editor-inner-blocks > .block-editor-block-list__layout', 'keys' => array( 'blocks', 'core/columns' ), 'get_properties' => array( '--stk-columns-column-gap', 'row-gap' ), 'block_gap_properties' => $columns_block_gap_properties, @@ -519,17 +519,17 @@ private function get_extended_element_settings() { private function get_extended_column_default_rules() { return array( 'extended-column-defaults' => array( - 'selector' => ':root', + 'selector' => '.wp-block-stackable-columns.stk-block-columns', 'declarations' => array( - '--stk-column-margin' => self::UPDATED_COLUMN_DEFAULTS[ 'column-margin' ][ 'desktop' ] . 'px', + '--stk-columns-spacing' => self::UPDATED_COLUMN_DEFAULTS[ 'column-margin' ][ 'desktop' ] . 'px', '--stk-columns-column-gap' => self::UPDATED_COLUMN_DEFAULTS[ 'columns-column-gap' ][ 'desktop' ] . 'px', ), ), 'extended-column-defaults-mobile' => array( 'rules_group' => '@media (max-width:767px)', - 'selector' => ':root', + 'selector' => '.wp-block-stackable-columns.stk-block-columns', 'declarations' => array( - '--stk-column-margin' => self::UPDATED_COLUMN_DEFAULTS[ 'column-margin' ][ 'mobile' ] . 'px', + '--stk-columns-spacing' => self::UPDATED_COLUMN_DEFAULTS[ 'column-margin' ][ 'mobile' ] . 'px', '--stk-columns-column-gap' => self::UPDATED_COLUMN_DEFAULTS[ 'columns-column-gap' ][ 'mobile' ] . 'px', ), ), From 075e10f47bc660ec3e9cab6368f5d0e8a42465eb Mon Sep 17 00:00:00 2001 From: Arukuen Date: Tue, 13 Oct 2026 11:21:24 +0800 Subject: [PATCH 07/10] fix: use the same shadow preset family in the editor and server --- src/hooks/use-preset-controls.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/hooks/use-preset-controls.js b/src/hooks/use-preset-controls.js index efa256049b..76843429a5 100644 --- a/src/hooks/use-preset-controls.js +++ b/src/hooks/use-preset-controls.js @@ -34,6 +34,7 @@ const PRESET_MAPPING = { settings: [ 'shadow', 'presets' ], defaultSizes: 'shadow.presets.default', defaultEnabled: 'shadow.defaultPresets', + useDefaultPresetsAsFallback: true, prefix: 'shadow', valueKey: 'shadow', }, @@ -79,6 +80,9 @@ export const usePresetControls = property => { let themePresets = useWordPressPresets ? _themePresets : [] const hasThemePresets = Array.isArray( themePresets ) && themePresets.length > 0 + const hasDefaultPresets = mapping.useDefaultPresetsAsFallback && + Array.isArray( wpDefaultPresets ) && wpDefaultPresets.length > 0 && + defaultSizesEnabled !== false // Merge theme presets with default presets when default sizes are enabled. // This happens when settings like `typography.defaultFontSizes` or `spacing.defaultSpacingSizes` @@ -101,7 +105,9 @@ export const usePresetControls = property => { // Get the theme/default presets if the user have one, else return the stackable presets const basePresets = hasThemePresets ? themePresets - : mapping.settings.reduce( ( acc, key ) => acc?.[ key ], DEFAULT_PRESETS.settings ) + : hasDefaultPresets + ? wpDefaultPresets + : mapping.settings.reduce( ( acc, key ) => acc?.[ key ], DEFAULT_PRESETS.settings ) // Returns the base presets overriden by the custom presets const getMergedPresets = () => { From 84d4bd73ff4c92938c06680cae51feba0098d3a1 Mon Sep 17 00:00:00 2001 From: Arukuen Date: Tue, 13 Oct 2026 16:58:21 +0800 Subject: [PATCH 08/10] fix: match the proper editor strcuture --- src/plugins/theme-block-style-inheritance/index.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/plugins/theme-block-style-inheritance/index.php b/src/plugins/theme-block-style-inheritance/index.php index 0f062e49ad..aa0b2cfc7a 100644 --- a/src/plugins/theme-block-style-inheritance/index.php +++ b/src/plugins/theme-block-style-inheritance/index.php @@ -499,10 +499,13 @@ private function get_extended_element_settings() { ), // The editor adds wrappers between the block and the element that owns the gap. 'extended-columns-gap-editor' => array( - 'selector' => ':where(.wp-block-stackable-columns.stk-block-columns) > .stk-block-content > .block-editor-inner-blocks > .block-editor-block-list__layout', + 'selector' => ':where(.wp-block-stackable-columns) > .stk-block-columns > .stk-block-content > .block-editor-inner-blocks > .block-editor-block-list__layout', 'keys' => array( 'blocks', 'core/columns' ), - 'get_properties' => array( '--stk-columns-column-gap', 'row-gap' ), - 'block_gap_properties' => $columns_block_gap_properties, + 'get_properties' => array( '--stk-column-gap', 'row-gap' ), + 'block_gap_properties' => array( + 'column' => '--stk-column-gap', + 'row' => 'row-gap', + ), ), ); } @@ -519,7 +522,7 @@ private function get_extended_element_settings() { private function get_extended_column_default_rules() { return array( 'extended-column-defaults' => array( - 'selector' => '.wp-block-stackable-columns.stk-block-columns', + 'selector' => '.wp-block-stackable-columns.stk-block-columns, .wp-block-stackable-columns > .stk-block-columns', 'declarations' => array( '--stk-columns-spacing' => self::UPDATED_COLUMN_DEFAULTS[ 'column-margin' ][ 'desktop' ] . 'px', '--stk-columns-column-gap' => self::UPDATED_COLUMN_DEFAULTS[ 'columns-column-gap' ][ 'desktop' ] . 'px', @@ -527,7 +530,7 @@ private function get_extended_column_default_rules() { ), 'extended-column-defaults-mobile' => array( 'rules_group' => '@media (max-width:767px)', - 'selector' => '.wp-block-stackable-columns.stk-block-columns', + 'selector' => '.wp-block-stackable-columns.stk-block-columns, .wp-block-stackable-columns > .stk-block-columns', 'declarations' => array( '--stk-columns-spacing' => self::UPDATED_COLUMN_DEFAULTS[ 'column-margin' ][ 'mobile' ] . 'px', '--stk-columns-column-gap' => self::UPDATED_COLUMN_DEFAULTS[ 'columns-column-gap' ][ 'mobile' ] . 'px', From 49a4c49812e288d30ac66f9c10838032e40076ed Mon Sep 17 00:00:00 2001 From: Arukuen Date: Mon, 19 Oct 2026 10:45:03 +0800 Subject: [PATCH 09/10] fix: missing option defaults to false --- src/plugins/theme-block-style-inheritance/index.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/theme-block-style-inheritance/index.php b/src/plugins/theme-block-style-inheritance/index.php index aa0b2cfc7a..06cf2b4919 100644 --- a/src/plugins/theme-block-style-inheritance/index.php +++ b/src/plugins/theme-block-style-inheritance/index.php @@ -63,7 +63,7 @@ function register_block_style_inheritance() { 'description' => __( 'Use updated Columns spacing and inherit structural styles from equivalent WordPress blocks', STACKABLE_I18N ), 'sanitize_callback' => 'rest_sanitize_boolean', 'show_in_rest' => true, - 'default' => true, + 'default' => false, ) ); } @@ -107,7 +107,7 @@ function block_style_inheritance_set_default( $old_version, $new_version ) { */ function add_block_style_inheritance( $current_css ) { $disable_block_style_inheritance = get_option( 'stackable_disable_block_style_inheritance' ); - $enable_extended_block_style_inheritance = (bool) get_option( 'stackable_enable_extended_block_style_inheritance', true ); + $enable_extended_block_style_inheritance = (bool) get_option( 'stackable_enable_extended_block_style_inheritance', false ); $extended_column_default_rules = $enable_extended_block_style_inheritance ? $this->get_extended_column_default_rules() : array(); @@ -771,7 +771,7 @@ function get_declarations( $styles, $style_declarations, $element, $element_keys public function add_setting( $settings ) { $settings['stackable_disable_block_style_inheritance'] = get_option( 'stackable_disable_block_style_inheritance', false ); - $enable_extended_block_style_inheritance = (bool) get_option( 'stackable_enable_extended_block_style_inheritance', true ); + $enable_extended_block_style_inheritance = (bool) get_option( 'stackable_enable_extended_block_style_inheritance', false ); $settings['stackable_enable_extended_block_style_inheritance'] = $enable_extended_block_style_inheritance; // Override editor placeholders only when the updated Columns defaults are active. $settings['stackable_column_spacing_defaults'] = $enable_extended_block_style_inheritance From d5b0461f559db26b77832702521d57a5abf0a473 Mon Sep 17 00:00:00 2001 From: Arukuen Date: Mon, 19 Oct 2026 11:00:22 +0800 Subject: [PATCH 10/10] fix: move updated column gap behavior behind extended inheritance --- src/block/columns/style.scss | 2 +- src/plugins/theme-block-style-inheritance/index.php | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/block/columns/style.scss b/src/block/columns/style.scss index decb7bf6c5..148cf2fd50 100644 --- a/src/block/columns/style.scss +++ b/src/block/columns/style.scss @@ -10,7 +10,7 @@ } .stk-block-columns > .stk-block-content { - --stk-column-gap: var(--stk-columns-column-gap, 0px); // For nested columns, this takes precedence. + --stk-column-gap: 0px; // For nested columns, this takes precedence. column-gap: var(--stk-column-gap); margin-left: auto; margin-right: auto; diff --git a/src/plugins/theme-block-style-inheritance/index.php b/src/plugins/theme-block-style-inheritance/index.php index 06cf2b4919..a4183647c8 100644 --- a/src/plugins/theme-block-style-inheritance/index.php +++ b/src/plugins/theme-block-style-inheritance/index.php @@ -521,6 +521,12 @@ private function get_extended_element_settings() { */ private function get_extended_column_default_rules() { return array( + 'extended-column-gap' => array( + 'selector' => '.wp-block-stackable-columns.stk-block-columns > .stk-block-content, .wp-block-stackable-columns > .stk-block-columns > .stk-block-content', + 'declarations' => array( + '--stk-column-gap' => 'var(--stk-columns-column-gap, 0px)', + ), + ), 'extended-column-defaults' => array( 'selector' => '.wp-block-stackable-columns.stk-block-columns, .wp-block-stackable-columns > .stk-block-columns', 'declarations' => array(