diff --git a/packages/block-editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js index b8f3c72863318..b2094a5585cca 100644 --- a/packages/block-editor/src/components/inner-blocks/index.js +++ b/packages/block-editor/src/components/inner-blocks/index.js @@ -10,6 +10,7 @@ import { useViewportMatch } from '@wordpress/compose'; import { forwardRef, useRef } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; import { getBlockType, withBlockContentContext } from '@wordpress/blocks'; +import { __experimentalBoxControl as BoxControl } from '@wordpress/components'; /** * Internal dependencies @@ -29,6 +30,8 @@ import { useBlockEditContext } from '../block-edit/context'; import useBlockSync from '../provider/use-block-sync'; import { defaultLayout, LayoutProvider } from './layout'; +const { __Visualizer: BoxControlVisualizer } = BoxControl; + /** * InnerBlocks is a component which allows a single block to have multiple blocks * as children. The UncontrolledInnerBlocks component is used whenever the inner @@ -179,11 +182,17 @@ export function useInnerBlocksProps( props = {}, options = {} ) { } ), children: ( - + <> + + + ), }; } diff --git a/packages/block-editor/src/hooks/margin.js b/packages/block-editor/src/hooks/margin.js new file mode 100644 index 0000000000000..cdb9ab8b934b8 --- /dev/null +++ b/packages/block-editor/src/hooks/margin.js @@ -0,0 +1,90 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Platform } from '@wordpress/element'; +import { getBlockSupport } from '@wordpress/blocks'; +import { __experimentalBoxControl as BoxControl } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { cleanEmptyObject } from './utils'; +import { SPACING_SUPPORT_KEY } from './padding'; +import useEditorFeature from '../components/use-editor-feature'; + +const hasMarginSupport = ( blockName ) => { + const spacingSupport = getBlockSupport( blockName, SPACING_SUPPORT_KEY ); + return spacingSupport && spacingSupport.margin; +}; + +/** + * Inspector control panel containing the line height related configuration + * + * @param {Object} props + * + * @return {WPElement} Line height edit element. + */ +export function MarginEdit( props ) { + const { + name: blockName, + attributes: { style }, + setAttributes, + } = props; + + const customUnits = useEditorFeature( 'spacing.units' ); + const units = customUnits?.map( ( unit ) => ( { + value: unit, + label: unit, + } ) ); + + if ( ! hasMarginSupport( blockName ) ) { + return null; + } + + const onChange = ( next ) => { + const spacing = style && style.spacing; + const newStyle = { + ...style, + spacing: { + ...spacing, + margin: next, + }, + }; + + setAttributes( { + style: cleanEmptyObject( newStyle ), + } ); + }; + + const onChangeShowVisualizer = ( next ) => { + const spacing = style && style.visualizers; + const newStyle = { + ...style, + visualizers: { + ...spacing, + margin: next, + }, + }; + + setAttributes( { + style: cleanEmptyObject( newStyle ), + } ); + }; + + return Platform.select( { + web: ( + <> + + + ), + native: null, + } ); +} diff --git a/packages/block-editor/src/hooks/padding.js b/packages/block-editor/src/hooks/padding.js index 4f2d5ea51754c..124083076428c 100644 --- a/packages/block-editor/src/hooks/padding.js +++ b/packages/block-editor/src/hooks/padding.js @@ -16,7 +16,7 @@ export const SPACING_SUPPORT_KEY = 'spacing'; const hasPaddingSupport = ( blockName ) => { const spacingSupport = getBlockSupport( blockName, SPACING_SUPPORT_KEY ); - return spacingSupport && spacingSupport.padding !== false; + return spacingSupport && spacingSupport.padding; }; /** @@ -32,7 +32,6 @@ export function PaddingEdit( props ) { attributes: { style }, setAttributes, } = props; - const customUnits = useEditorFeature( 'spacing.units' ); const units = customUnits?.map( ( unit ) => ( { value: unit, @@ -44,9 +43,11 @@ export function PaddingEdit( props ) { } const onChange = ( next ) => { + const spacing = style && style.spacing; const newStyle = { ...style, spacing: { + ...spacing, padding: next, }, }; @@ -57,9 +58,11 @@ export function PaddingEdit( props ) { }; const onChangeShowVisualizer = ( next ) => { + const spacing = style && style.visualizers; const newStyle = { ...style, visualizers: { + ...spacing, padding: next, }, }; @@ -77,6 +80,7 @@ export function PaddingEdit( props ) { onChange={ onChange } onChangeShowVisualizer={ onChangeShowVisualizer } label={ __( 'Padding' ) } + type="padding" units={ units } /> diff --git a/packages/block-editor/src/hooks/style.js b/packages/block-editor/src/hooks/style.js index 85b8247743b38..fad294849d502 100644 --- a/packages/block-editor/src/hooks/style.js +++ b/packages/block-editor/src/hooks/style.js @@ -20,6 +20,7 @@ import { BORDER_SUPPORT_KEY, BorderPanel } from './border'; import { COLOR_SUPPORT_KEY, ColorEdit } from './color'; import { TypographyPanel, TYPOGRAPHY_SUPPORT_KEYS } from './typography'; import { SPACING_SUPPORT_KEY, PaddingEdit } from './padding'; +import { MarginEdit } from './margin'; import SpacingPanelControl from '../components/spacing-panel-control'; const styleSupportKeys = [ @@ -167,6 +168,7 @@ export const withBlockControls = createHigherOrderComponent( hasSpacingSupport && ( + ), ]; diff --git a/packages/block-library/src/column/block.json b/packages/block-library/src/column/block.json index 02c2084dabb3f..182efe8ece3b1 100644 --- a/packages/block-library/src/column/block.json +++ b/packages/block-library/src/column/block.json @@ -19,6 +19,10 @@ "supports": { "anchor": true, "reusable": false, - "html": false + "html": false, + "spacing": { + "padding": true, + "margin": true + } } } diff --git a/packages/block-library/src/column/edit.js b/packages/block-library/src/column/edit.js index c3275d3667de4..5ee85664e94a1 100644 --- a/packages/block-library/src/column/edit.js +++ b/packages/block-library/src/column/edit.js @@ -27,7 +27,7 @@ import { __ } from '@wordpress/i18n'; import { CSS_UNITS } from '../columns/utils'; function ColumnEdit( { - attributes: { verticalAlignment, width, templateLock = false }, + attributes: { verticalAlignment, width, style, templateLock = false }, setAttributes, clientId, } ) { @@ -63,7 +63,9 @@ function ColumnEdit( { const widthWithUnit = Number.isFinite( width ) ? width + '%' : width; const blockProps = useBlockProps( { className: classes, - style: widthWithUnit ? { flexBasis: widthWithUnit } : undefined, + style: widthWithUnit + ? { flexBasis: widthWithUnit, ...style } + : { ...style }, } ); const innerBlocksProps = useInnerBlocksProps( blockProps, { templateLock, diff --git a/packages/block-library/src/columns/block.json b/packages/block-library/src/columns/block.json index 7aa5ff3e1bc8c..b2698ac36f795 100644 --- a/packages/block-library/src/columns/block.json +++ b/packages/block-library/src/columns/block.json @@ -17,6 +17,10 @@ "color": { "gradients": true, "link": true + }, + "spacing": { + "padding": true, + "margin": true } }, "editorStyle": "wp-block-columns-editor", diff --git a/packages/block-library/src/columns/edit.js b/packages/block-library/src/columns/edit.js index 9ad79099af20a..61fdeba324fc8 100644 --- a/packages/block-library/src/columns/edit.js +++ b/packages/block-library/src/columns/edit.js @@ -52,7 +52,7 @@ function ColumnsEditContainer( { updateColumns, clientId, } ) { - const { verticalAlignment } = attributes; + const { verticalAlignment, style } = attributes; const { count } = useSelect( ( select ) => { @@ -69,6 +69,7 @@ function ColumnsEditContainer( { const blockProps = useBlockProps( { className: classes, + style, } ); const innerBlocksProps = useInnerBlocksProps( blockProps, { allowedBlocks: ALLOWED_BLOCKS, diff --git a/packages/block-library/src/cover/block.json b/packages/block-library/src/cover/block.json index b0b2d58bde2aa..373992096862f 100644 --- a/packages/block-library/src/cover/block.json +++ b/packages/block-library/src/cover/block.json @@ -55,7 +55,8 @@ "align": true, "html": false, "spacing": { - "padding": true + "padding": true, + "margin": true } }, "editorStyle": "wp-block-cover-editor", diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index a018117c3dc2c..e9503b6d330e5 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -564,8 +564,8 @@ function CoverEdit( { data-url={ url } >
diff --git a/packages/block-library/src/separator/block.json b/packages/block-library/src/separator/block.json index 2983a93a86190..25685ec1f16b9 100644 --- a/packages/block-library/src/separator/block.json +++ b/packages/block-library/src/separator/block.json @@ -12,7 +12,10 @@ }, "supports": { "anchor": true, - "align": ["center","wide","full"] + "align": ["center","wide","full"], + "spacing": { + "margin": true + } }, "editorStyle": "wp-block-separator-editor", "style": "wp-block-separator" diff --git a/packages/block-library/src/separator/edit.js b/packages/block-library/src/separator/edit.js index f611abde42a62..1006a6044f330 100644 --- a/packages/block-library/src/separator/edit.js +++ b/packages/block-library/src/separator/edit.js @@ -6,17 +6,19 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { HorizontalRule } from '@wordpress/components'; +import { __experimentalBoxControl as BoxControl } from '@wordpress/components'; import { withColors, useBlockProps } from '@wordpress/block-editor'; /** * Internal dependencies */ import SeparatorSettings from './separator-settings'; -function SeparatorEdit( { color, setColor, className } ) { +const { __Visualizer: BoxControlVisualizer } = BoxControl; + +function SeparatorEdit( { attributes, color, setColor, className } ) { return ( <> - + > + +
); diff --git a/packages/block-library/src/separator/edit.native.js b/packages/block-library/src/separator/edit.native.js new file mode 100644 index 0000000000000..f611abde42a62 --- /dev/null +++ b/packages/block-library/src/separator/edit.native.js @@ -0,0 +1,36 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { HorizontalRule } from '@wordpress/components'; +import { withColors, useBlockProps } from '@wordpress/block-editor'; +/** + * Internal dependencies + */ +import SeparatorSettings from './separator-settings'; + +function SeparatorEdit( { color, setColor, className } ) { + return ( + <> + + + + ); +} + +export default withColors( 'color', { textColor: 'color' } )( SeparatorEdit ); diff --git a/packages/blocks/src/api/constants.js b/packages/blocks/src/api/constants.js index eb95932528aee..41e9048f268e3 100644 --- a/packages/blocks/src/api/constants.js +++ b/packages/blocks/src/api/constants.js @@ -58,6 +58,11 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = { support: [ 'spacing', 'padding' ], properties: [ 'top', 'right', 'bottom', 'left' ], }, + margin: { + value: [ 'spacing', 'margin' ], + support: [ 'spacing', 'margin' ], + properties: [ 'top', 'right', 'bottom', 'left' ], + }, textDecoration: { value: [ 'typography', 'textDecoration' ], support: [ '__experimentalTextDecoration' ], diff --git a/packages/components/src/box-control/all-input-control.js b/packages/components/src/box-control/all-input-control.js index 1f1260898505d..101cbdcc832b7 100644 --- a/packages/components/src/box-control/all-input-control.js +++ b/packages/components/src/box-control/all-input-control.js @@ -6,7 +6,7 @@ import { noop } from 'lodash'; * Internal dependencies */ import UnitControl from './unit-control'; -import { LABELS, getAllValue, isValuesMixed, isValuesDefined } from './utils'; +import { LABELS, isValuesMixed, isValuesDefined } from './utils'; export default function AllInputControl( { onChange = noop, @@ -16,9 +16,11 @@ export default function AllInputControl( { values, ...props } ) { - const allValue = getAllValue( values ); + const allValues = Object.values( values ); + const [ singleValue ] = allValues.slice( 0, 1 ); const hasValues = isValuesDefined( values ); const isMixed = hasValues && isValuesMixed( values ); + const disableUnits = isMixed || singleValue?.match( /^(auto|calc)/ ); const allPlaceholder = isMixed ? LABELS.mixed : null; @@ -58,9 +60,9 @@ export default function AllInputControl( { return ( ( event ) => { @@ -39,7 +40,6 @@ export default function BoxInputControls( { const createHandleOnChange = ( side ) => ( next, { event } ) => { const { altKey } = event; const nextValues = { ...values }; - nextValues[ side ] = next; /** @@ -82,6 +82,8 @@ export default function BoxInputControls( { onHoverOn={ createHandleOnHoverOn( 'top' ) } onHoverOff={ createHandleOnHoverOff( 'top' ) } label={ LABELS.top } + disableUnits={ top?.match( /^(auto|calc)/ ) } + type={ `${ type }Top` } /> diff --git a/packages/components/src/box-control/styles/box-control-styles.js b/packages/components/src/box-control/styles/box-control-styles.js index 78cf6ceb9a696..2224bfb2bb672 100644 --- a/packages/components/src/box-control/styles/box-control-styles.js +++ b/packages/components/src/box-control/styles/box-control-styles.js @@ -28,7 +28,7 @@ export const HeaderControlWrapper = styled( Flex )` export const UnitControlWrapper = styled.div` box-sizing: border-box; - max-width: 80px; + max-width: 100px; `; export const LayoutContainer = styled( Flex )` @@ -65,7 +65,7 @@ const unitControlMarginStyles = ( { isFirst } ) => { }; export const UnitControl = styled( BaseUnitControl )` - max-width: 60px; + max-width: 100px; ${ unitControlBorderRadiusStyles }; ${ unitControlMarginStyles }; `; diff --git a/packages/components/src/box-control/utils.js b/packages/components/src/box-control/utils.js index c2bd4baddd215..fe21367dff43a 100644 --- a/packages/components/src/box-control/utils.js +++ b/packages/components/src/box-control/utils.js @@ -22,6 +22,10 @@ export const LABELS = { mixed: __( 'Mixed' ), }; +export const CUSTOM_VALUES = { + AUTO: 'auto', +}; + export const DEFAULT_VALUES = { top: null, right: null, @@ -36,6 +40,11 @@ export const DEFAULT_VISUALIZER_VALUES = { left: false, }; +export const DEFAULT_VISUALIZER_SPACING_VALUES = { + padding: DEFAULT_VISUALIZER_VALUES, + margin: DEFAULT_VISUALIZER_VALUES, +}; + /** * Gets an items with the most occurance within an array * https://stackoverflow.com/a/20762713 @@ -82,7 +91,6 @@ export function getAllValue( values = {} ) { * simple truthy check. */ const allValue = isNumber( value ) ? `${ value }${ unit }` : null; - return allValue; } @@ -93,10 +101,8 @@ export function getAllValue( values = {} ) { * @return {boolean} Whether values are mixed. */ export function isValuesMixed( values = {} ) { - const allValue = getAllValue( values ); - const isMixed = isNaN( parseFloat( allValue ) ); - - return isMixed; + const vals = Object.values( values ); + return vals.some( ( val ) => val !== vals[ 0 ] ); } /** @@ -112,3 +118,40 @@ export function isValuesDefined( values ) { ! isEmpty( Object.values( values ).filter( Boolean ) ) ); } + +const sideStyles = { + margin: { + top: { transform: 'translateY(-100%)' }, + right: { transform: 'translateX(100%)' }, + bottom: { transform: 'translateY(100%)' }, + left: { transform: 'translateX(-100%)' }, + }, + padding: { + top: null, + right: null, + bottom: null, + left: null, + }, +}; + +/** + * Modifies the style properties of each side. + * + * @param {string} type of field + * @return {function(*, [*, *]): *} reducer function adding additional styles + */ + +export function extendStyles( type ) { + return ( acc, [ side, value ] ) => { + const styles = sideStyles[ type ][ side ]; + return { + ...acc, + [ side ]: { + style: { + ...styles, + [ side ]: value, + }, + }, + }; + }; +} diff --git a/packages/components/src/box-control/visualizer.js b/packages/components/src/box-control/visualizer.js index ad36cdcf30f7b..6ae9718316a86 100644 --- a/packages/components/src/box-control/visualizer.js +++ b/packages/components/src/box-control/visualizer.js @@ -13,70 +13,88 @@ import { BottomView, LeftView, } from './styles/box-control-visualizer-styles'; -import { DEFAULT_VALUES, DEFAULT_VISUALIZER_VALUES } from './utils'; +import { + DEFAULT_VISUALIZER_VALUES, + DEFAULT_VISUALIZER_SPACING_VALUES, + DEFAULT_VALUES, + extendStyles, +} from './utils'; export default function BoxControlVisualizer( { children, - showValues = DEFAULT_VISUALIZER_VALUES, - values: valuesProp = DEFAULT_VALUES, + showValues = DEFAULT_VISUALIZER_SPACING_VALUES, + values, ...props } ) { const isPositionAbsolute = ! children; - return ( - - ); + const valuesProp = { + margin: { ...DEFAULT_VALUES, ...values?.margin }, + padding: { ...DEFAULT_VALUES, ...values?.padding }, + }; + + return Object.entries( showValues ).map( ( [ key, value ] ) => { + return ( + + ); + } ); } -function Sides( { showValues = DEFAULT_VISUALIZER_VALUES, values } ) { - const { top, right, bottom, left } = values; +function Sides( { showValues = DEFAULT_VISUALIZER_VALUES, ...props } ) { + const { top, right, bottom, left } = Object.entries( props.values ).reduce( + extendStyles( props.type ), + {} + ); return ( <> - - - - + + + + ); } function Top( { isVisible = false, value } ) { - const height = value; + const { top: height, ...styles } = value; const animationProps = useSideAnimation( height ); const isActive = animationProps.isActive || isVisible; - - return ; + return ; } function Right( { isVisible = false, value } ) { - const width = value; + const { right: width, ...styles } = value; const animationProps = useSideAnimation( width ); const isActive = animationProps.isActive || isVisible; - return ; + return ; } function Bottom( { isVisible = false, value } ) { - const height = value; + const { bottom: height, ...styles } = value; const animationProps = useSideAnimation( height ); const isActive = animationProps.isActive || isVisible; - - return ; + return ; } function Left( { isVisible = false, value } ) { - const width = value; + const { left: width, ...styles } = value; const animationProps = useSideAnimation( width ); const isActive = animationProps.isActive || isVisible; - return ; + return ; } /** diff --git a/packages/components/src/number-control/index.js b/packages/components/src/number-control/index.js index cbcca8ed0e20f..128f58a03e245 100644 --- a/packages/components/src/number-control/index.js +++ b/packages/components/src/number-control/index.js @@ -93,9 +93,11 @@ export function NumberControl( nextValue = subtract( nextValue, incrementalValue ); } - nextValue = roundClamp( nextValue, min, max, incrementalValue ); - - state.value = nextValue; + if ( ! isNaN( nextValue ) ) { + state.value = roundClamp( nextValue, min, max ); + } else { + state.value = nextValue; + } } /** @@ -155,7 +157,11 @@ export function NumberControl( type === inputControlActionTypes.PRESS_ENTER || type === inputControlActionTypes.COMMIT ) { - state.value = roundClamp( currentValue, min, max ); + if ( ! isNaN( currentValue ) ) { + state.value = roundClamp( currentValue, min, max ); + } else { + state.value = currentValue; + } } return state; diff --git a/packages/components/src/number-control/test/index.js b/packages/components/src/number-control/test/index.js index 883b7f6d9591f..eb47d75f53fad 100644 --- a/packages/components/src/number-control/test/index.js +++ b/packages/components/src/number-control/test/index.js @@ -133,7 +133,7 @@ describe( 'NumberControl', () => { input.focus(); fireKeyDown( { keyCode: UP, shiftKey: true } ); - expect( input.value ).toBe( '20' ); + expect( input.value ).toBe( '15' ); } ); it( 'should increment by custom shiftStep on key UP + shift press', () => { @@ -143,7 +143,7 @@ describe( 'NumberControl', () => { input.focus(); fireKeyDown( { keyCode: UP, shiftKey: true } ); - expect( input.value ).toBe( '100' ); + expect( input.value ).toBe( '105' ); } ); it( 'should increment but be limited by max on shiftStep', () => { @@ -211,7 +211,7 @@ describe( 'NumberControl', () => { } ); it( 'should decrement by shiftStep on key DOWN + shift press', () => { - render( ); + render( ); const input = getInput(); input.focus(); @@ -227,7 +227,7 @@ describe( 'NumberControl', () => { input.focus(); fireKeyDown( { keyCode: DOWN, shiftKey: true } ); - expect( input.value ).toBe( '-100' ); + expect( input.value ).toBe( '-95' ); } ); it( 'should decrement but be limited by min on shiftStep', () => { diff --git a/packages/components/src/unit-control/index.js b/packages/components/src/unit-control/index.js index 9b0b0c42d645c..f4a9e7585ba21 100644 --- a/packages/components/src/unit-control/index.js +++ b/packages/components/src/unit-control/index.js @@ -45,7 +45,12 @@ function UnitControl( }, ref ) { - const [ value, initialUnit ] = getParsedValue( valueProp, unitProp, units ); + const [ value, initialUnit ] = getParsedValue( + valueProp, + unitProp, + units, + props.type + ); const [ unit, setUnit ] = useControlledState( unitProp, { initial: initialUnit, } ); @@ -65,7 +70,8 @@ function UnitControl( * Customizing the onChange callback. * This allows as to broadcast a combined value+unit to onChange. */ - next = getValidParsedUnit( next, units, value, unit ).join( '' ); + const { type } = props; + next = getValidParsedUnit( next, units, value, unit, type ).join( '' ); onChange( next, changeProps ); }; @@ -90,11 +96,13 @@ function UnitControl( refParsedValue.current = null; return; } + const [ parsedValue, parsedUnit ] = getValidParsedUnit( event.target.value, units, value, - unit + unit, + props.type ); refParsedValue.current = parsedValue; @@ -105,7 +113,7 @@ function UnitControl( ); const changeProps = { event, data }; - onChange( `${ parsedValue }${ parsedUnit }`, changeProps ); + onChange( `${ parsedValue }`, changeProps ); onUnitChange( parsedUnit, changeProps ); setUnit( parsedUnit ); @@ -163,7 +171,7 @@ function UnitControl( { } ); it( 'should decrement value on DOWN press', () => { - let state = 50; + let state = '50px'; const setState = ( nextState ) => ( state = nextState ); render( ); @@ -104,7 +104,7 @@ describe( 'UnitControl', () => { } ); it( 'should decrement value on DOWN + SHIFT press, with step', () => { - let state = 50; + let state = '50px'; const setState = ( nextState ) => ( state = nextState ); render( ); diff --git a/packages/components/src/unit-control/utils.js b/packages/components/src/unit-control/utils.js index 8abd21dea85bf..0a57003ffc71d 100644 --- a/packages/components/src/unit-control/utils.js +++ b/packages/components/src/unit-control/utils.js @@ -24,12 +24,17 @@ export const DEFAULT_UNIT = CSS_UNITS[ 0 ]; * @param {number|string} value Value * @param {string} unit Unit value * @param {Array} units Units to derive from. - * @return {Array} The extracted number and unit. + * @param {string} type Proptype. + * @return {Array} The extracted number and unit. */ -export function getParsedValue( value, unit, units ) { +export function getParsedValue( value, unit, units, type ) { const initialValue = unit ? `${ value }${ unit }` : value; - return parseUnit( initialValue, units ); + if ( value && type && getCSSValues( value, type ) ) { + return getCSSValues( value, type ); + } + + return parseUnit( initialValue ); } /** @@ -47,12 +52,18 @@ export function hasUnits( units ) { * * @param {string} initialValue Value to parse * @param {Array} units Units to derive from. + * @param {string} fallbackUnit fallback unit * @return {Array} The extracted number and unit. */ -export function parseUnit( initialValue, units = CSS_UNITS ) { +export function parseUnit( initialValue, units = CSS_UNITS, fallbackUnit ) { const value = String( initialValue ).trim(); - let num = parseFloat( value, 10 ); + + if ( typeof initialValue === 'number' ) { + const fallBack = fallbackUnit || DEFAULT_UNIT.value; + return [ num, fallBack ]; + } + num = isNaN( num ) ? '' : num; const unitMatch = value.match( /[\d.\-\+]*\s*(.*)/ )[ 1 ]; @@ -78,26 +89,80 @@ export function parseUnit( initialValue, units = CSS_UNITS ) { * @param {Array} units Units to derive from. * @param {number|string} fallbackValue The fallback value. * @param {string} fallbackUnit The fallback value. - * @return {Array} The extracted number and unit. + * @param {string?} type The styleType. + * @return {Array} The extracted number and unit. */ -export function getValidParsedUnit( next, units, fallbackValue, fallbackUnit ) { - const [ parsedValue, parsedUnit ] = parseUnit( next, units ); - let baseValue = parsedValue; - let baseUnit; - - if ( isNaN( parsedValue ) || parsedValue === '' ) { - baseValue = fallbackValue; +export function getValidParsedUnit( + next, + units, + fallbackValue, + fallbackUnit, + type +) { + if ( ! parseUnit( next, units, fallbackUnit )[ 0 ] ) { + return ( + ( next && type && getCSSValues( next, type ) ) || [ + fallbackValue, + fallbackUnit, + ] + ); } - baseUnit = parsedUnit || fallbackUnit; + const [ parsedValue, parsedUnit ] = parseUnit( next, units, fallbackUnit ); + + return ! parsedUnit + ? [ parsedValue, fallbackUnit ] + : [ parsedValue, parsedUnit ]; +} + +function getCSSValues( value, styleKey ) { + const element = document.createElement( 'div' ).style; + + element[ styleKey ] = value; + + if ( value && isExpression( value ) && isUsingTheSameUnits( value ) ) { + const units = getUnits( value )[ 0 ]; + const expression = getArithmeticExpression( value ); + + // eslint-disable-next-line no-eval + const result = `${ eval( expression ) }${ units }`; - /** - * If no unit is found, attempt to use the first value from the collection - * of units as a default fallback. - */ - if ( hasUnits( units ) && ! baseUnit ) { - baseUnit = units[ 0 ]?.value; + element[ styleKey ] = result; + + return ( + element[ styleKey ].includes( result ) && [ + // eslint-disable-next-line no-eval + eval( expression ), + units, + ] + ); + } + + if ( parseUnit( value )[ 1 ] ) { + return parseUnit( value ); } - return [ baseValue, baseUnit ]; + return element[ styleKey ].includes( value ) && [ value ]; +} + +function isExpression( value ) { + return value.match( /^calc\(/g )?.length; +} + +function isUsingTheSameUnits( value ) { + return Array.from( new Set( getUnits( value ) ) ).length === 1; +} + +function getUnits( value ) { + return value.match( /[px|r?em]{2,3}/g ); +} + +/** + * Leaves only arithmetic symbols from a string. + * + * @param {string} value + * @return {string} expression + */ +function getArithmeticExpression( value ) { + return value.replace( /[^-()\d/*+.]/g, '' ); }