diff --git a/packages/block-library/src/button/edit.native.js b/packages/block-library/src/button/edit.native.js index 923ed47282efb..a06f5d06adccb 100644 --- a/packages/block-library/src/button/edit.native.js +++ b/packages/block-library/src/button/edit.native.js @@ -19,11 +19,14 @@ import { } from '@wordpress/block-editor'; import { PanelBody, - RangeControl, ToolbarGroup, ToolbarButton, LinkSettingsNavigation, + UnitControl, + getValueAndUnit, BottomSheetSelectControl, + CSS_UNITS, + filterUnitsWithSettings, } from '@wordpress/components'; import { Component } from '@wordpress/element'; import { withSelect, withDispatch } from '@wordpress/data'; @@ -88,6 +91,9 @@ class ButtonEdit extends Component { super( props ); this.onChangeText = this.onChangeText.bind( this ); this.onChangeBorderRadius = this.onChangeBorderRadius.bind( this ); + this.onChangeBorderRadiusUnit = this.onChangeBorderRadiusUnit.bind( + this + ); this.onClearSettings = this.onClearSettings.bind( this ); this.onLayout = this.onLayout.bind( this ); this.onSetMaxWidth = this.onSetMaxWidth.bind( this ); @@ -100,11 +106,15 @@ class ButtonEdit extends Component { this.onRemove = this.onRemove.bind( this ); this.getPlaceholderWidth = this.getPlaceholderWidth.bind( this ); + const borderRadius = props?.attributes?.style?.border?.radius; + const { valueUnit = 'px' } = getValueAndUnit( borderRadius ) || {}; + this.state = { maxWidth: INITIAL_MAX_WIDTH, isLinkSheetVisible: false, isButtonFocused: true, placeholderTextWidth: 0, + borderRadiusUnit: valueUnit, }; this.linkSettingsActions = [ @@ -237,17 +247,33 @@ class ButtonEdit extends Component { } onChangeBorderRadius( newRadius ) { + const { setAttributes, attributes } = this.props; + const { borderRadiusUnit } = this.state; + const { style } = attributes; + const newStyle = this.getNewStyle( style, newRadius, borderRadiusUnit ); + + setAttributes( { style: newStyle } ); + } + + onChangeBorderRadiusUnit( newRadiusUnit ) { const { setAttributes, attributes } = this.props; const { style } = attributes; - const newStyle = { + const borderRadius = this.getBorderRadiusValue( + attributes?.style?.border?.radius + ); + const newStyle = this.getNewStyle( style, borderRadius, newRadiusUnit ); + setAttributes( { style: newStyle } ); + this.setState( { borderRadiusUnit: newRadiusUnit } ); + } + + getNewStyle( style, radius, radiusUnit ) { + return { ...style, border: { ...style?.border, - radius: newRadius, + radius: `${ radius }${ radiusUnit }`, // Store the value with the unit so that it works as expected. }, }; - - setAttributes( { style: newStyle } ); } onShowLinkSettings() { @@ -368,6 +394,14 @@ class ButtonEdit extends Component { } } + getBorderRadiusValue( borderRadius, defaultBorderRadius ) { + const valueAndUnit = getValueAndUnit( borderRadius ); + if ( Number.isInteger( parseInt( valueAndUnit?.valueToConvert ) ) ) { + return parseFloat( valueAndUnit.valueToConvert ); + } + return defaultBorderRadius; + } + render() { const { attributes, @@ -387,7 +421,12 @@ class ButtonEdit extends Component { align = 'center', width, } = attributes; - const { maxWidth, isButtonFocused, placeholderTextWidth } = this.state; + const { + maxWidth, + isButtonFocused, + placeholderTextWidth, + borderRadiusUnit, + } = this.state; const { paddingTop: spacing, borderWidth } = styles.defaultButton; if ( parentWidth === 0 ) { @@ -395,13 +434,18 @@ class ButtonEdit extends Component { } const borderRadius = buttonStyle?.border?.radius; + const borderRadiusValue = this.getBorderRadiusValue( + borderRadius, + styles.defaultButton.borderRadius + ); - const borderRadiusValue = Number.isInteger( borderRadius ) - ? borderRadius - : styles.defaultButton.borderRadius; + const buttonBorderRadiusValue = + borderRadiusUnit === 'px' || borderRadiusUnit === '%' + ? borderRadiusValue + : Math.floor( 14 * borderRadiusValue ); // lets assume that the font size is set to 14px; TO get a nicer preview. const outlineBorderRadius = - borderRadiusValue > 0 - ? borderRadiusValue + spacing + borderWidth + buttonBorderRadiusValue > 0 + ? buttonBorderRadiusValue + spacing + borderWidth : 0; // To achieve proper expanding and shrinking `RichText` on iOS, there is a need to set a `minWidth` @@ -433,7 +477,7 @@ class ButtonEdit extends Component { { this.getPlaceholderWidth( placeholderText ) } @@ -504,12 +548,20 @@ class ButtonEdit extends Component { { this.getLinkSettings( false ) } - { + // Register all core blocks + registerCoreBlocks(); +} ); + +afterAll( () => { + // Clean up registered blocks + getBlockTypes().forEach( ( block ) => { + unregisterBlockType( block.name ); + } ); +} ); + +describe( 'when a button is shown', () => { + it( 'adjusts the border radius', async () => { + const initialHtml = ` +
+ +
+ `; + const { getByA11yLabel, getByTestId } = await initializeEditor( { + initialHtml, + } ); + + const buttonsBlock = await waitFor( () => + getByA11yLabel( /Buttons Block\. Row 1/ ) + ); + fireEvent.press( buttonsBlock ); + + // onLayout event has to be explicitly dispatched in BlockList component, + // otherwise the inner blocks are not rendered. + const innerBlockListWrapper = await waitFor( () => + within( buttonsBlock ).getByTestId( 'block-list-wrapper' ) + ); + fireEvent( innerBlockListWrapper, 'layout', { + nativeEvent: { + layout: { + width: 100, + }, + }, + } ); + + const buttonInnerBlock = await waitFor( () => + within( buttonsBlock ).getByA11yLabel( /Button Block\. Row 1/ ) + ); + fireEvent.press( buttonInnerBlock ); + + const settingsButton = await waitFor( () => + getByA11yLabel( 'Open Settings' ) + ); + fireEvent.press( settingsButton ); + + const radiusSlider = await waitFor( () => + getByTestId( 'Slider Border Radius' ) + ); + fireEvent( radiusSlider, 'valueChange', '25' ); + + const expectedHtml = ` +
+ +
+`; + expect( getEditorHtml() ).toBe( expectedHtml ); + } ); +} ); diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index b596bd50ac43a..b95446752bab3 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -175,7 +175,6 @@ function ColumnEdit( { onChange={ onChange } onComplete={ onChangeWidth } onUnitChange={ onChangeUnit } - decimalNum={ 1 } value={ getWidths( columns )[ selectedColumnIndex ] } diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 40afe71538fde..63e0aeea895d1 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -203,7 +203,6 @@ function ColumnsEditContainer( { ? 100 : undefined } - decimalNum={ 1 } value={ getWidths( innerWidths )[ index ] } onChange={ ( nextWidth ) => { onChange( nextWidth, valueUnit, column.clientId ); diff --git a/packages/blocks/src/api/test/validation.js b/packages/blocks/src/api/test/validation.js index b13fb948721ad..f55f77bfc4e64 100644 --- a/packages/blocks/src/api/test/validation.js +++ b/packages/blocks/src/api/test/validation.js @@ -187,6 +187,12 @@ describe( 'validation', () => { expect( normalizedLength ).toBe( '50%' ); } ); + + it( 'adds leading zero to percentage', () => { + const normalizedLength = getNormalizedLength( '.5%' ); + + expect( normalizedLength ).toBe( '0.5%' ); + } ); } ); describe( 'getNormalizedStyleValue()', () => { @@ -208,6 +214,12 @@ describe( 'validation', () => { expect( normalizedValue ).toBe( '44% 0 18em 0' ); } ); + it( 'add leading zero to units that have it missing', () => { + const normalizedValue = getNormalizedStyleValue( '.23% .75em' ); + + expect( normalizedValue ).toBe( '0.23% 0.75em' ); + } ); + it( 'leaves zero values in calc() expressions alone', () => { const normalizedValue = getNormalizedStyleValue( 'calc(0em + 5px)' diff --git a/packages/blocks/src/api/validation/index.js b/packages/blocks/src/api/validation/index.js index 025912afdbc90..8cfbd1c051681 100644 --- a/packages/blocks/src/api/validation/index.js +++ b/packages/blocks/src/api/validation/index.js @@ -334,7 +334,15 @@ export function isEquivalentTextTokens( * @return {string} Normalized CSS length value. */ export function getNormalizedLength( value ) { - return 0 === parseFloat( value ) ? '0' : value; + if ( 0 === parseFloat( value ) ) { + return '0'; + } + // Normalize strings with floats to always include a leading zero. + if ( value.indexOf( '.' ) === 0 ) { + return '0' + value; + } + + return value; } /** diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js index 2e1a4d84c612d..8a9471e86f095 100644 --- a/packages/components/src/index.native.js +++ b/packages/components/src/index.native.js @@ -54,6 +54,10 @@ export { default as UnitControl, useCustomUnits as __experimentalUseCustomUnits, } from './unit-control'; +export { + CSS_UNITS as CSS_UNITS, + filterUnitsWithSettings as filterUnitsWithSettings, +} from './unit-control/utils'; export { default as Disabled } from './disabled'; // Higher-Order Components diff --git a/packages/components/src/mobile/bottom-sheet/stepper-cell/index.native.js b/packages/components/src/mobile/bottom-sheet/stepper-cell/index.native.js index 23f6d8cf44d16..677164951255a 100644 --- a/packages/components/src/mobile/bottom-sheet/stepper-cell/index.native.js +++ b/packages/components/src/mobile/bottom-sheet/stepper-cell/index.native.js @@ -57,8 +57,9 @@ class BottomSheetStepperCell extends Component { onIncrementValue() { const { step, max, onChange, value, decimalNum } = this.props; - const newValue = toFixed( value + step, decimalNum ); - + let newValue = toFixed( value + step, decimalNum ); + newValue = + parseInt( newValue ) === newValue ? parseInt( newValue ) : newValue; if ( newValue <= max || max === undefined ) { onChange( newValue ); this.setState( { @@ -70,8 +71,9 @@ class BottomSheetStepperCell extends Component { onDecrementValue() { const { step, min, onChange, value, decimalNum } = this.props; - const newValue = toFixed( value - step, decimalNum ); - + let newValue = toFixed( value - step, decimalNum ); + newValue = + parseInt( newValue ) === newValue ? parseInt( newValue ) : newValue; if ( newValue >= min ) { onChange( newValue ); this.setState( { @@ -238,7 +240,7 @@ class BottomSheetStepperCell extends Component { label={ label } onChange={ onChange } defaultValue={ `${ inputValue }` } - value={ inputValue } + value={ value } min={ min } step={ 1 } decimalNum={ decimalNum } diff --git a/packages/components/src/mobile/utils/index.native.js b/packages/components/src/mobile/utils/index.native.js index 4ec8dca02392d..119b217639e5e 100644 --- a/packages/components/src/mobile/utils/index.native.js +++ b/packages/components/src/mobile/utils/index.native.js @@ -5,6 +5,8 @@ export function removeNonDigit( text, decimalNum ) { } export function toFixed( num, decimalNum = 0 ) { - const fixed = Math.pow( 10, decimalNum < 0 ? 0 : decimalNum ); - return Math.floor( num * fixed ) / fixed; + const decimalOffset = decimalNum < 0 ? 0 : decimalNum; + return Number.parseFloat( + Number.parseFloat( num ).toFixed( decimalOffset ) + ); } diff --git a/packages/components/src/mobile/utils/test/index.native.js b/packages/components/src/mobile/utils/test/index.native.js index c665d8b9f4cb4..ace5792110b99 100644 --- a/packages/components/src/mobile/utils/test/index.native.js +++ b/packages/components/src/mobile/utils/test/index.native.js @@ -50,7 +50,26 @@ describe( 'toFixed', () => { it( 'function returns the number applying `decimalNum`', () => { const result = toFixed( '123.4567', 2 ); - expect( result ).toBe( 123.45 ); + expect( result ).toBe( 123.46 ); + } ); + + it( 'function returns the number applying `decimalNum` all point numbers', () => { + const toCheck = [ + 1.01, + 1.02, + 1.03, + 1.04, + 1.05, + 1.06, + 1.07, + 1.08, + 1.09, + 1.1, + ]; + toCheck.forEach( ( num ) => { + const result = toFixed( num, 2 ); + expect( result ).toBe( num ); + } ); } ); it( 'function returns number without decimals if `decimalNum` is negative', () => { diff --git a/packages/components/src/unit-control/index.native.js b/packages/components/src/unit-control/index.native.js index 4a7e0ff0a5da4..ddb5acb1b8b33 100644 --- a/packages/components/src/unit-control/index.native.js +++ b/packages/components/src/unit-control/index.native.js @@ -38,7 +38,6 @@ function UnitControl( { units = CSS_UNITS, unit, getStylesFromColorScheme, - decimalNum, ...props } ) { const pickerRef = useRef(); @@ -105,6 +104,16 @@ function UnitControl( { [ anchorNodeRef?.current ] ); + const getDecimal = ( step ) => { + // Return the decimal offset based on the step size. + // if step size is 0.1 we expect the offset to be 1. + // for example 12 + 0.1 we would expect the see 12.1 (not 12.10 or 12 ); + // steps are defined in the CSS_UNITS and they vary from unit to unit. + const stepToString = step; + const splitStep = stepToString.toString().split( '.' ); + return splitStep[ 1 ] ? splitStep[ 1 ].length : 0; + }; + const renderUnitPicker = useCallback( () => { return ( @@ -121,7 +130,7 @@ function UnitControl( { ) : null } ); - }, [ pickerRef, units, onUnitChange, getAnchor ] ); + }, [ pickerRef, units, onUnitChange, getAnchor, renderUnitButton ] ); let step = props.step; @@ -134,6 +143,8 @@ function UnitControl( { step = activeUnit?.step ?? 1; } + const decimalNum = getDecimal( step ); + return ( <> { unit !== '%' ? ( @@ -147,7 +158,7 @@ function UnitControl( { step={ step } defaultValue={ initialControlValue } shouldDisplayTextInput - decimalNum={ unit === 'px' ? 0 : decimalNum } + decimalNum={ decimalNum } openUnitPicker={ onPickerPresent } unitLabel={ parseA11yLabelForUnit( unit ) } { ...props } diff --git a/test/native/__mocks__/styleMock.js b/test/native/__mocks__/styleMock.js index a49f07231a498..6ce96cb8fc14f 100644 --- a/test/native/__mocks__/styleMock.js +++ b/test/native/__mocks__/styleMock.js @@ -129,4 +129,7 @@ module.exports = { textInput: { color: 'black', }, + buttonNoBg: { + color: 'orange', + }, };