diff --git a/packages/block-editor/src/components/block-list/block-list-item.native.js b/packages/block-editor/src/components/block-list/block-list-item.native.js index 8623514b1b44c2..230b8d720e6b71 100644 --- a/packages/block-editor/src/components/block-list/block-list-item.native.js +++ b/packages/block-editor/src/components/block-list/block-list-item.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { View } from 'react-native'; +import { View, Dimensions } from 'react-native'; /** * WordPress dependencies @@ -9,11 +9,7 @@ import { View } from 'react-native'; import { Component } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; -import { - ReadableContentView, - WIDE_ALIGNMENTS, - ALIGNMENT_BREAKPOINTS, -} from '@wordpress/components'; +import { ReadableContentView, alignmentHelpers } from '@wordpress/components'; /** * Internal dependencies @@ -26,6 +22,13 @@ const stretchStyle = { flex: 1, }; +const { + isFullWidth, + isWideWidth, + isWider, + isContainerRelated, +} = alignmentHelpers; + export class BlockListItem extends Component { constructor() { super( ...arguments ); @@ -51,39 +54,69 @@ export class BlockListItem extends Component { blockAlignment, marginHorizontal, parentBlockAlignment, + hasParents, + blockName, + parentBlockName, + parentWidth, } = this.props; const { blockWidth } = this.state; - if ( blockAlignment === WIDE_ALIGNMENTS.alignments.full ) { - return 0; + if ( isFullWidth( blockAlignment ) ) { + if ( ! hasParents ) { + return 0; + } + return marginHorizontal; } - - if ( blockAlignment === WIDE_ALIGNMENTS.alignments.wide ) { + if ( isWideWidth( blockAlignment ) ) { return marginHorizontal; } + const screenWidth = Math.floor( Dimensions.get( 'window' ).width ); + if ( - parentBlockAlignment === WIDE_ALIGNMENTS.alignments.full && - blockWidth <= ALIGNMENT_BREAKPOINTS.medium + isFullWidth( parentBlockAlignment ) && + ! isWider( blockWidth, 'medium' ) ) { + if ( + isContainerRelated( blockName ) || + isWider( screenWidth, 'mobile' ) + ) { + return marginHorizontal; + } return marginHorizontal * 2; } + if ( + isContainerRelated( parentBlockName ) && + ! isContainerRelated( blockName ) + ) { + const isScreenWidthEqual = parentWidth === screenWidth; + if ( isScreenWidthEqual || isWider( screenWidth, 'mobile' ) ) { + return marginHorizontal * 2; + } + } + return marginHorizontal; } getContentStyles( readableContentViewStyle ) { - const { blockAlignment, hasParents } = this.props; - const isFullWidth = blockAlignment === WIDE_ALIGNMENTS.alignments.full; + const { + blockAlignment, + blockName, + hasParents, + parentBlockName, + } = this.props; return [ readableContentViewStyle, - isFullWidth && + isFullWidth( blockAlignment ) && ! hasParents && { width: styles.fullAlignment.width, }, - isFullWidth && - hasParents && { + ! blockAlignment && + hasParents && + ! isContainerRelated( parentBlockName ) && + isContainerRelated( blockName ) && { paddingHorizontal: styles.fullAlignmentPadding.paddingLeft, }, ]; @@ -98,15 +131,23 @@ export class BlockListItem extends Component { shouldShowInsertionPointAfter, contentResizeMode, shouldShowInnerBlockAppender, + parentWidth, + marginHorizontal, + blockName, ...restProps } = this.props; const readableContentViewStyle = contentResizeMode === 'stretch' && stretchStyle; - return ( @@ -169,7 +211,7 @@ export default compose( [ const isReadOnly = getSettings().readOnly; const block = __unstableGetBlockWithoutInnerBlocks( clientId ); - const { attributes } = block || {}; + const { attributes, name } = block || {}; const { align } = attributes || {}; const parents = getBlockParents( clientId, true ); const hasParents = !! parents.length; @@ -178,6 +220,7 @@ export default compose( [ : {}; const { align: parentBlockAlignment } = parentBlock?.attributes || {}; + const { name: parentBlockName } = parentBlock || {}; return { shouldShowInsertionPointBefore, @@ -186,6 +229,8 @@ export default compose( [ hasParents, blockAlignment: align, parentBlockAlignment, + blockName: name, + parentBlockName, }; } ), diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 27df35cf3242a3..d07512fca1bd54 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -10,7 +10,7 @@ import { Component, createRef } from '@wordpress/element'; import { GlobalStylesContext, getMergedGlobalStyles, - WIDE_ALIGNMENTS, + alignmentHelpers, } from '@wordpress/components'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; @@ -28,6 +28,8 @@ import BlockEdit from '../block-edit'; import BlockInvalidWarning from './block-invalid-warning'; import BlockMobileToolbar from '../block-mobile-toolbar'; +const { isFullWidth, isWider, isContainerRelated } = alignmentHelpers; + function BlockForType( { attributes, clientId, @@ -122,9 +124,10 @@ class BlockListBlock extends Component { getBlockWidth( { nativeEvent } ) { const { layout } = nativeEvent; const { blockWidth } = this.state; + const layoutWidth = Math.floor( layout.width ); - if ( blockWidth !== layout.width ) { - this.setState( { blockWidth: layout.width } ); + if ( blockWidth !== layoutWidth ) { + this.setState( { blockWidth: layoutWidth } ); } } @@ -166,12 +169,12 @@ class BlockListBlock extends Component { marginVertical, marginHorizontal, isInnerBlockSelected, + name, } = this.props; if ( ! attributes || ! blockType ) { return null; } - const { blockWidth } = this.state; const { align } = attributes; const accessibilityLabel = getAccessibleBlockLabel( @@ -181,8 +184,10 @@ class BlockListBlock extends Component { ); const accessible = ! ( isSelected || isInnerBlockSelected ); - const isFullWidth = align === WIDE_ALIGNMENTS.alignments.full; const screenWidth = Math.floor( Dimensions.get( 'window' ).width ); + const isScreenWidthEqual = blockWidth === screenWidth; + const isScreenWidthWider = blockWidth < screenWidth; + const isFullWidthToolbar = isFullWidth( align ) || isScreenWidthEqual; return ( ) } { isSelected && ( @@ -249,7 +264,7 @@ class BlockListBlock extends Component { } blockWidth={ blockWidth } anchorNodeRef={ this.anchorNodeRef.current } - isFullWidth={ isFullWidth } + isFullWidth={ isFullWidthToolbar } /> ) } diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index 62be86ec7662d5..e9ee6b35334f82 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -23,6 +23,11 @@ right: 0; } +.containerBorderFullWidth { + left: -$solid-border-space / 2; + right: -$solid-border-space / 2; +} + .dimmed { opacity: $dimmed-opacity; } @@ -51,6 +56,11 @@ margin-right: -$block-edge-to-content; } +.containerToolbar { + width: 100%; + align-self: center; +} + .solidBorder { position: absolute; top: -$solid-border-space; diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index f6ed4418a99953..6a4322ce460269 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -15,6 +15,7 @@ import { KeyboardAwareFlatList, ReadableContentView, WIDE_ALIGNMENTS, + alignmentHelpers, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; @@ -47,6 +48,7 @@ const getStyles = ( stylesMemo[ styleName ] = computedStyles; return computedStyles; }; +const { isWider } = alignmentHelpers; export class BlockList extends Component { constructor() { @@ -169,6 +171,8 @@ export class BlockList extends Component { isFloatingToolbarVisible, isStackedHorizontally, horizontalAlignment, + contentResizeMode, + blockWidth, } = this.props; const { parentScrollRef } = extraProps; @@ -185,6 +189,10 @@ export class BlockList extends Component { marginVertical: isRootList ? 0 : -marginVertical, marginHorizontal: isRootList ? 0 : -marginHorizontal, }; + + const isContentStretch = contentResizeMode === 'stretch'; + const isMultiBlocks = blockClientIds.length > 1; + return ( { const [ fillsLength, setFillsLength ] = useState( null ); - const wrapBlockSettings = blockWidth < BREAKPOINTS.wrapSettings; - const wrapBlockMover = blockWidth <= BREAKPOINTS.wrapMover; + const [ appenderWidth, setAppenderWidth ] = useState( 0 ); + const spacingValue = styles.toolbar.marginLeft * 2; + + function onLayout( { nativeEvent } ) { + const { layout } = nativeEvent; + const layoutWidth = Math.floor( layout.width ); + if ( layoutWidth !== appenderWidth ) { + setAppenderWidth( nativeEvent.layout.width ); + } + } + + const wrapBlockSettings = + blockWidth < BREAKPOINTS.wrapSettings || + appenderWidth - spacingValue < BREAKPOINTS.wrapSettings; + const wrapBlockMover = + blockWidth <= BREAKPOINTS.wrapMover || + appenderWidth - spacingValue <= BREAKPOINTS.wrapMover; return ( { ! wrapBlockMover && ( ); diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 16e03fadc5fdd3..4fec805c235ea4 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -1,14 +1,14 @@ /** * External dependencies */ -import { View } from 'react-native'; +import { View, Dimensions } from 'react-native'; /** * WordPress dependencies */ import { withSelect } from '@wordpress/data'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; -import { useEffect, useState } from '@wordpress/element'; +import { useEffect, useState, useCallback } from '@wordpress/element'; import { InnerBlocks, BlockControls, @@ -20,6 +20,7 @@ import { FooterMessageControl, UnitControl, getValueAndUnit, + alignmentHelpers, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; /** @@ -27,7 +28,14 @@ import { __ } from '@wordpress/i18n'; */ import styles from './editor.scss'; import ColumnsPreview from './column-preview'; -import { getWidths, getWidthWithUnit, CSS_UNITS } from '../columns/utils'; +import { + getWidths, + getWidthWithUnit, + isPercentageUnit, + CSS_UNITS, +} from '../columns/utils'; + +const { isWider } = alignmentHelpers; function ColumnEdit( { attributes, @@ -40,11 +48,14 @@ function ColumnEdit( { columns, selectedColumnIndex, parentAlignment, + clientId, } ) { const { verticalAlignment, width } = attributes; const { valueUnit = '%' } = getValueAndUnit( width ) || {}; - const [ widthUnit, setWidthUnit ] = useState( valueUnit ); + const screenWidth = Math.floor( Dimensions.get( 'window' ).width ); + + const [ widthUnit, setWidthUnit ] = useState( valueUnit || '%' ); const updateAlignment = ( alignment ) => { setAttributes( { verticalAlignment: alignment } ); @@ -70,15 +81,44 @@ function ColumnEdit( { const onChangeUnit = ( nextUnit ) => { setWidthUnit( nextUnit ); - const tempWidth = parseFloat( + const widthWithoutUnit = parseFloat( width || getWidths( columns )[ selectedColumnIndex ] ); setAttributes( { - width: getWidthWithUnit( tempWidth, nextUnit ), + width: getWidthWithUnit( widthWithoutUnit, nextUnit ), } ); }; + const onChange = ( nextWidth ) => { + if ( isPercentageUnit( widthUnit ) || ! widthUnit ) { + return; + } + onChangeWidth( nextWidth ); + }; + + const renderAppender = useCallback( () => { + const { width: blockWidth } = contentStyle[ clientId ]; + const isScreenWidthEqual = blockWidth === screenWidth; + + if ( isSelected ) { + return ( + + + + ); + } + return null; + }, [ contentStyle[ clientId ], screenWidth, isSelected, hasChildren ] ); + if ( ! isSelected && ! hasChildren ) { return ( ); @@ -108,11 +148,12 @@ function ColumnEdit( { diff --git a/packages/block-library/src/column/editor.native.scss b/packages/block-library/src/column/editor.native.scss index e5e2e853f59b73..6c52b88af035e4 100644 --- a/packages/block-library/src/column/editor.native.scss +++ b/packages/block-library/src/column/editor.native.scss @@ -16,7 +16,7 @@ } .innerBlocksBottomSpace { - margin-bottom: $block-selected-to-content; + margin-bottom: $block-edge-to-content; } .is-vertically-aligned-top { @@ -57,3 +57,13 @@ .columnIndicatorDark { background-color: $gray-20; } + +.columnAppender { + margin-left: $grid-unit-20; + margin-right: $grid-unit-20; +} + +.wideColumnAppender { + margin-left: $grid-unit-10; + margin-right: $grid-unit-10; +} diff --git a/packages/block-library/src/columns/columnCalculations.native.js b/packages/block-library/src/columns/columnCalculations.native.js new file mode 100644 index 00000000000000..46d334b9d0e8ea --- /dev/null +++ b/packages/block-library/src/columns/columnCalculations.native.js @@ -0,0 +1,178 @@ +/** + * WordPress dependencies + */ +import { + ALIGNMENT_BREAKPOINTS, + convertUnitToMobile, +} from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { getColumnWidths, getWidths } from './utils'; +import styles from './editor.scss'; + +/** + * Maximum number of columns in a row + * + * @type {number} + */ +const MAX_COLUMNS_NUM_IN_ROW = 3; + +/** + * Minimum width of column + * + * @type {number} + */ +const MIN_WIDTH = styles.columnsContainer?.minWidth; + +/** + * Container margin value + * + * @type {number} + */ +const MARGIN = styles.columnsContainer?.marginLeft; + +export const getColumnsInRow = ( width, columnCount ) => { + if ( width ) { + if ( width < ALIGNMENT_BREAKPOINTS.mobile ) { + // show only 1 Column in row for mobile breakpoint container width + return 1; + } else if ( width <= ALIGNMENT_BREAKPOINTS.medium ) { + // show the maximum number of columns in a row for large breakpoint container width + return Math.min( + Math.max( 1, columnCount ), + MAX_COLUMNS_NUM_IN_ROW + ); + } + // show all Column in one row + return columnCount; + } +}; + +export const calculateContainerWidth = ( containerWidth, columnsInRow ) => + 2 * MARGIN + containerWidth - columnsInRow * 2 * MARGIN; + +export const getContentWidths = ( + columnsInRow, + width, + columnCount, + innerColumns, + globalStyles +) => { + const widths = {}; + const columnWidthsWithUnits = getWidths( innerColumns, false ); + const columnWidths = getColumnWidths( innerColumns, columnCount ); + + // Array of column width attribute values + const columnWidthsValues = columnWidthsWithUnits.map( ( v ) => + convertUnitToMobile( { width }, globalStyles, v ) + ); + + // The sum of column width attribute values + const columnWidthsSum = columnWidthsValues.reduce( + ( acc, curr ) => acc + curr, + 0 + ); + + // Array of ratios of each column width attribute value to their sum + const columnRatios = columnWidthsValues.map( + ( colWidth ) => colWidth / columnWidthsSum + ); + + // Array of calculated column width for its ratio + const columnWidthsPerRatio = columnRatios.map( + ( columnRatio ) => + columnRatio * calculateContainerWidth( width, columnsInRow ) + ); + + // Array of columns whose calculated width is lower than minimum width value + const filteredColumnWidthsPerRatio = columnWidthsPerRatio.filter( + ( columnWidthPerRatio ) => columnWidthPerRatio <= MIN_WIDTH + ); + + // Container width to be divided. If there are some results within `filteredColumnWidthsPerRatio` + // there is a need to reduce the main width by multiplying number + // of results in `filteredColumnWidthsPerRatio` and minimum width value + const baseContainerWidth = + width - filteredColumnWidthsPerRatio.length * MIN_WIDTH; + + // The minimum percentage ratio for which column width is equal minimum width value + const minPercentageRatio = + MIN_WIDTH / calculateContainerWidth( width, columnsInRow ); + + // The sum of column widths which ratio is higher than `minPercentageRatio` + const largeColumnsWidthsSum = columnRatios + .map( ( ratio, index ) => { + if ( ratio > minPercentageRatio ) { + return columnWidthsValues[ index ]; + } + return 0; + } ) + .reduce( ( acc, curr ) => acc + curr, 0 ); + + const containerWidth = calculateContainerWidth( + baseContainerWidth, + columnsInRow + ); + + let columnWidth = + calculateContainerWidth( width, columnsInRow ) / columnsInRow; + let maxColumnWidth = columnWidth; + + innerColumns.forEach( + ( { attributes: innerColumnAttributes, clientId } ) => { + const attributeWidth = convertUnitToMobile( + { width }, + globalStyles, + innerColumnAttributes.width || columnWidths[ clientId ] + ); + const proportionalRatio = attributeWidth / columnWidthsSum; + const percentageRatio = attributeWidth / width; + const initialColumnWidth = proportionalRatio * containerWidth; + + if ( columnCount === 1 && width > ALIGNMENT_BREAKPOINTS.medium ) { + // Exactly one column inside columns on the breakpoint higher than medium + // has to take a percentage of the full width + columnWidth = percentageRatio * containerWidth; + } else if ( columnsInRow > 1 ) { + if ( width > ALIGNMENT_BREAKPOINTS.medium ) { + if ( initialColumnWidth <= MIN_WIDTH ) { + // Column width cannot be lower than minimum 32px + columnWidth = MIN_WIDTH; + } else if ( initialColumnWidth > MIN_WIDTH ) { + // Column width has to be the result of multiplying the container width and + // the ratio of attribute and the sum of widths of columns wider than 32px + columnWidth = + ( attributeWidth / largeColumnsWidthsSum ) * + containerWidth; + } + + maxColumnWidth = columnWidth; + + if ( Math.round( columnWidthsSum ) < width ) { + // In case that column width attribute values does not exceed 100, each column + // should have attribute percentage of container width + const newColumnWidth = percentageRatio * containerWidth; + if ( newColumnWidth <= MIN_WIDTH ) { + columnWidth = MIN_WIDTH; + } else { + columnWidth = newColumnWidth; + } + } + } else if ( width < ALIGNMENT_BREAKPOINTS.medium ) { + // On the breakpoint lower than medium each column inside columns + // has to take equal part of container width + columnWidth = + calculateContainerWidth( width, columnsInRow ) / + columnsInRow; + } + } + widths[ clientId ] = { + width: Math.floor( columnWidth ), + maxWidth: Math.floor( maxColumnWidth ), + }; + } + ); + return widths; +}; diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 453cf8f9eed3f0..68de0e05ca1b81 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -1,9 +1,8 @@ /** * External dependencies */ -import { View } from 'react-native'; +import { View, Dimensions } from 'react-native'; import { dropRight, times, map, compact, delay } from 'lodash'; - /** * WordPress dependencies */ @@ -14,6 +13,8 @@ import { FooterMessageControl, UnitControl, getValueAndUnit, + GlobalStylesContext, + alignmentHelpers, } from '@wordpress/components'; import { InspectorControls, @@ -23,7 +24,13 @@ import { BlockVariationPicker, } from '@wordpress/block-editor'; import { withDispatch, useSelect } from '@wordpress/data'; -import { useEffect, useState, useMemo } from '@wordpress/element'; +import { + useEffect, + useState, + useContext, + useMemo, + useCallback, +} from '@wordpress/element'; import { useResizeObserver } from '@wordpress/compose'; import { createBlock } from '@wordpress/blocks'; import { columns } from '@wordpress/icons'; @@ -39,8 +46,14 @@ import { toWidthPrecision, getWidths, getWidthWithUnit, + isPercentageUnit, CSS_UNITS, } from './utils'; +import { + getColumnsInRow, + calculateContainerWidth, + getContentWidths, +} from './columnCalculations.native'; import ColumnsPreview from '../column/column-preview'; /** @@ -69,17 +82,7 @@ const DEFAULT_COLUMNS_NUM = 2; */ const MIN_COLUMNS_NUM = 1; -/** - * Maximum number of columns in a row - * - * @type {number} - */ -const MAX_COLUMNS_NUM_IN_ROW = 3; - -const BREAKPOINTS = { - mobile: 480, - large: 768, -}; +const { isWider, isFullWidth } = alignmentHelpers; function ColumnsEditContainer( { attributes, @@ -87,71 +90,48 @@ function ColumnsEditContainer( { updateColumns, columnCount, isSelected, - onAddNextColumn, onDeleteBlock, innerColumns, updateInnerColumnWidth, + editorSidebarOpened, } ) { const [ resizeListener, sizes ] = useResizeObserver(); const [ columnsInRow, setColumnsInRow ] = useState( MIN_COLUMNS_NUM ); + const screenWidth = Math.floor( Dimensions.get( 'window' ).width ); + const globalStyles = useContext( GlobalStylesContext ); - const { verticalAlignment } = attributes; + const { verticalAlignment, align } = attributes; const { width } = sizes || {}; - const newColumnCount = columnCount || DEFAULT_COLUMNS_NUM; - useEffect( () => { - updateColumns( columnCount, newColumnCount ); - }, [] ); + if ( columnCount === 0 ) { + const newColumnCount = columnCount || DEFAULT_COLUMNS_NUM; - useEffect( () => { - if ( width ) { - setColumnsInRow( getColumnsInRow( width, newColumnCount ) ); + updateColumns( columnCount, newColumnCount ); } - }, [ columnCount ] ); + }, [] ); useEffect( () => { if ( width ) { - setColumnsInRow( getColumnsInRow( width, columnCount ) ); - } - }, [ width ] ); - - const contentStyle = useMemo( () => { - const minWidth = Math.min( width, styles.columnsContainer.maxWidth ); - const columnBaseWidth = minWidth / columnsInRow; - - let columnWidth = columnBaseWidth; - if ( columnsInRow > 1 ) { - const margins = - columnsInRow * - Math.min( columnsInRow, MAX_COLUMNS_NUM_IN_ROW ) * - styles.columnMargin.marginLeft; - columnWidth = ( minWidth - margins ) / columnsInRow; - } - return { width: columnWidth }; - }, [ width, columnsInRow ] ); - - const getColumnsInRow = ( containerWidth, columnsNumber ) => { - if ( containerWidth < BREAKPOINTS.mobile ) { - // show only 1 Column in row for mobile breakpoint container width - return 1; - } else if ( containerWidth < BREAKPOINTS.large ) { - // show the maximum number of columns in a row for large breakpoint container width - return Math.min( - Math.max( 1, columnCount ), - MAX_COLUMNS_NUM_IN_ROW - ); + if ( getColumnsInRow( width, columnCount ) !== columnsInRow ) { + setColumnsInRow( getColumnsInRow( width, columnCount ) ); + } } - // show all Column in one row - return Math.max( 1, columnsNumber ); - }; + }, [ width, columnCount ] ); const renderAppender = () => { + const isEqualWidth = width === screenWidth; + if ( isSelected ) { return ( - + ); @@ -159,6 +139,22 @@ function ColumnsEditContainer( { return null; }; + const contentWidths = useMemo( + () => + getContentWidths( + columnsInRow, + width, + columnCount, + innerColumns, + globalStyles + ), + [ width, columnsInRow, columnCount, innerColumns, globalStyles ] + ); + + const onAddBlock = useCallback( () => { + updateColumns( columnCount, columnCount + 1 ); + }, [ columnCount ] ); + const onChangeWidth = ( nextWidth, valueUnit, columnId ) => { const widthWithUnit = getWidthWithUnit( nextWidth, valueUnit ); @@ -166,13 +162,26 @@ function ColumnsEditContainer( { }; const onChangeUnit = ( nextUnit, index, columnId ) => { - const tempWidth = parseFloat( getWidths( innerColumns )[ index ] ); - const widthWithUnit = getWidthWithUnit( tempWidth, nextUnit ); + const widthWithoutUnit = parseFloat( + getWidths( innerColumns )[ index ] + ); + const widthWithUnit = getWidthWithUnit( widthWithoutUnit, nextUnit ); updateInnerColumnWidth( widthWithUnit, columnId ); }; + const onChange = ( nextWidth, valueUnit, columnId ) => { + if ( isPercentageUnit( valueUnit ) || ! valueUnit ) { + return; + } + onChangeWidth( nextWidth, valueUnit, columnId ); + }; + const getColumnsSliders = () => { + if ( ! editorSidebarOpened || ! isSelected ) { + return null; + } + return innerColumns.map( ( column, index ) => { const { valueUnit = '%' } = getValueAndUnit( column.attributes.width ) || {}; @@ -183,15 +192,22 @@ function ColumnsEditContainer( { getWidths( innerColumns ).length }` } min={ 1 } - max={ valueUnit === '%' || ! valueUnit ? 100 : undefined } + max={ + isPercentageUnit( valueUnit ) || ! valueUnit + ? 100 + : undefined + } decimalNum={ 1 } value={ getWidths( innerColumns )[ index ] } onChange={ ( nextWidth ) => { - onChangeWidth( nextWidth, valueUnit, column.clientId ); + onChange( nextWidth, valueUnit, column.clientId ); } } onUnitChange={ ( nextUnit ) => onChangeUnit( nextUnit, index, column.clientId ) } + onComplete={ ( nextWidth ) => { + onChangeWidth( nextWidth, valueUnit, column.clientId ); + } } unit={ valueUnit } units={ CSS_UNITS } preview={ @@ -247,12 +263,17 @@ function ColumnsEditContainer( { horizontal={ true } allowedBlocks={ ALLOWED_BLOCKS } contentResizeMode="stretch" - onAddBlock={ onAddNextColumn } + onAddBlock={ onAddBlock } onDeleteBlock={ columnCount === 1 ? onDeleteBlock : undefined } - contentStyle={ contentStyle } - parentWidth={ width } + blockWidth={ width } + contentStyle={ contentWidths } + parentWidth={ + isFullWidth( align ) && columnCount === 0 + ? screenWidth + : calculateContainerWidth( width, columnsInRow ) + } /> ) } diff --git a/packages/block-library/src/columns/editor.native.scss b/packages/block-library/src/columns/editor.native.scss index bae76a50d6d2e1..d212677477927a 100644 --- a/packages/block-library/src/columns/editor.native.scss +++ b/packages/block-library/src/columns/editor.native.scss @@ -1,5 +1,6 @@ .columnsContainer { - max-width: $content-width; + min-width: $block-edge-to-content * 2; + margin-left: $block-edge-to-content; } .innerBlocksSelected { @@ -10,3 +11,7 @@ margin: $block-edge-to-content / 2; } +.columnAppender { + margin-left: $grid-unit-20; + margin-right: $grid-unit-20; +} diff --git a/packages/block-library/src/columns/utils.js b/packages/block-library/src/columns/utils.js index 70fcc5841d41a0..0d9c5d9db59623 100644 --- a/packages/block-library/src/columns/utils.js +++ b/packages/block-library/src/columns/utils.js @@ -162,7 +162,7 @@ export function getWidths( blocks, withParsing = true ) { export function getWidthWithUnit( width, unit ) { width = 0 > parseFloat( width ) ? '0' : width; - if ( unit === '%' ) { + if ( isPercentageUnit( unit ) ) { width = Math.min( width, 100 ); } @@ -198,3 +198,14 @@ export const CSS_UNITS = [ default: '', }, ]; + +/** + * Returns a boolean whether passed unit is percentage + * + * @param {string} unit Column width unit. + * + * @return {boolean} Whether unit is '%'. + */ +export function isPercentageUnit( unit ) { + return unit === '%'; +} diff --git a/packages/block-library/src/gallery/gallery.native.js b/packages/block-library/src/gallery/gallery.native.js index d465ca7daa4d9a..e83e89417177f0 100644 --- a/packages/block-library/src/gallery/gallery.native.js +++ b/packages/block-library/src/gallery/gallery.native.js @@ -20,7 +20,7 @@ import { BlockCaption } from '@wordpress/block-editor'; import { useState, useEffect } from '@wordpress/element'; import { mediaUploadSync } from '@wordpress/react-native-bridge'; import { useSelect } from '@wordpress/data'; -import { WIDE_ALIGNMENTS } from '@wordpress/components'; +import { alignmentHelpers } from '@wordpress/components'; const TILE_SPACING = 15; @@ -28,6 +28,8 @@ const TILE_SPACING = 15; const MAX_DISPLAYED_COLUMNS = 4; const MAX_DISPLAYED_COLUMNS_NARROW = 2; +const { isFullWidth } = alignmentHelpers; + export const Gallery = ( props ) => { const [ isCaptionSelected, setIsCaptionSelected ] = useState( false ); useEffect( mediaUploadSync, [] ); @@ -84,8 +86,6 @@ export const Gallery = ( props ) => { onFocusGalleryCaption(); }; - const isFullWidth = align === WIDE_ALIGNMENTS.alignments.full; - return ( { ); } ) } - + { mediaPlaceholder } ( ), - [ align, hasInnerBlocks ] + [ align, hasInnerBlocks, width ] ); if ( ! isSelected && ! hasInnerBlocks ) { @@ -62,10 +76,6 @@ function GroupEdit( { - + { resizeObserver } + ); } @@ -87,7 +101,9 @@ export default compose( [ getBlock, getBlockIndex, hasSelectedInnerBlock, + getBlockRootClientId, getSelectedBlockClientId, + getBlockAttributes, } = select( 'core/block-editor' ); const block = getBlock( clientId ); @@ -104,9 +120,13 @@ export default compose( [ isLastInnerBlockSelected = totalInnerBlocks === blockIndex; } + const parentId = getBlockRootClientId( clientId ); + const parentBlockAlignment = getBlockAttributes( parentId )?.align; + return { hasInnerBlocks, isLastInnerBlockSelected, + parentBlockAlignment, }; } ), withPreferredColorScheme, diff --git a/packages/block-library/src/group/editor.native.scss b/packages/block-library/src/group/editor.native.scss index d33982b2b36bf9..af4616c8e74a5a 100644 --- a/packages/block-library/src/group/editor.native.scss +++ b/packages/block-library/src/group/editor.native.scss @@ -32,9 +32,14 @@ margin-right: $block-edge-to-content; } -.fullWidthAppender { - margin-left: $block-edge-to-content; - margin-right: $block-edge-to-content; +.groupAppender { + margin-left: $grid-unit-20; + margin-right: $grid-unit-20; +} + +.wideGroupAppender { + margin-left: $grid-unit-10; + margin-right: $grid-unit-10; } .hasBackgroundAppender { diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js index ccfdaf84e17be1..57e33a8a0350f1 100644 --- a/packages/components/src/index.native.js +++ b/packages/components/src/index.native.js @@ -87,10 +87,12 @@ export { colorsUtils } from './mobile/color-settings/utils'; export { WIDE_ALIGNMENTS, ALIGNMENT_BREAKPOINTS, + alignmentHelpers, } from './mobile/utils/alignments'; // Hooks export { + convertUnitToMobile, useConvertUnitToMobile, getValueAndUnit, } from './mobile/utils/use-unit-converter-to-mobile'; diff --git a/packages/components/src/mobile/bottom-sheet/range-cell.native.js b/packages/components/src/mobile/bottom-sheet/range-cell.native.js index b7a37c66b9b61c..f041b2969fea3f 100644 --- a/packages/components/src/mobile/bottom-sheet/range-cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/range-cell.native.js @@ -66,11 +66,12 @@ class BottomSheetRangeCell extends Component { } onChange( nextValue ) { - const { onChange } = this.props; + const { onChange, onComplete } = this.props; this.setState( { sliderValue: nextValue, } ); onChange( nextValue ); + onComplete( nextValue ); } render() { @@ -89,6 +90,7 @@ class BottomSheetRangeCell extends Component { thumbTintColor = ! isIOS && '#00669b', preview, cellContainerStyle, + onComplete, shouldDisplayTextInput = true, children, decimalNum, @@ -146,6 +148,7 @@ class BottomSheetRangeCell extends Component { maximumTrackTintColor={ maximumTrackTintColor } thumbTintColor={ thumbTintColor } onValueChange={ this.onChangeValue } + onSlidingComplete={ onComplete } ref={ ( slider ) => { this.sliderRef = slider; } } diff --git a/packages/components/src/mobile/bottom-sheet/range-text-input.native.js b/packages/components/src/mobile/bottom-sheet/range-text-input.native.js index 0be4ca8dbb5633..efb9e64d85125d 100644 --- a/packages/components/src/mobile/bottom-sheet/range-text-input.native.js +++ b/packages/components/src/mobile/bottom-sheet/range-text-input.native.js @@ -39,8 +39,11 @@ class RangeTextInput extends Component { this.onSubmitEditing = this.onSubmitEditing.bind( this ); this.onChangeText = this.onChangeText.bind( this ); - const { value, defaultValue, min } = props; - const initialValue = value || defaultValue || min; + const { value, defaultValue, min, decimalNum } = props; + const initialValue = toFixed( + value || defaultValue || min, + decimalNum + ); const fontScale = this.getFontScale(); @@ -128,13 +131,6 @@ class RangeTextInput extends Component { onChange( validValue ); } - onChangeValue( initialValue ) { - const { decimalNum } = this.props; - initialValue = toFixed( initialValue, decimalNum ); - this.setState( { inputValue: initialValue } ); - this.updateValue( initialValue ); - } - onChangeText( textValue ) { const { decimalNum } = this.props; const inputValue = removeNonDigit( textValue, decimalNum ); @@ -194,7 +190,7 @@ class RangeTextInput extends Component { const valueFinalStyle = [ ! isIOS ? inputBorderStyles : verticalBorderStyle, { - width: 40 * fontScale, + width: 50 * fontScale, }, ]; diff --git a/packages/components/src/mobile/utils/alignments.native.js b/packages/components/src/mobile/utils/alignments.native.js index 03fdcbd93f5e86..219bdefb8a721c 100644 --- a/packages/components/src/mobile/utils/alignments.native.js +++ b/packages/components/src/mobile/utils/alignments.native.js @@ -3,7 +3,10 @@ export const WIDE_ALIGNMENTS = { wide: 'wide', full: 'full', }, - excludeBlocks: [ 'core/columns', 'core/heading' ], + // `innerContainers`: Group of blocks based on `InnerBlocks` component, + // used to nest other blocks inside + innerContainers: [ 'core/group', 'core/columns', 'core/column' ], + excludeBlocks: [ 'core/heading' ], }; export const ALIGNMENT_BREAKPOINTS = { @@ -11,4 +14,22 @@ export const ALIGNMENT_BREAKPOINTS = { large: 820, medium: 768, small: 680, + mobile: 480, +}; + +const isFullWidth = ( align ) => align === WIDE_ALIGNMENTS.alignments.full; + +const isWideWidth = ( align ) => align === WIDE_ALIGNMENTS.alignments.wide; + +const isWider = ( width, breakpoint ) => + width > ALIGNMENT_BREAKPOINTS[ breakpoint ]; + +const isContainerRelated = ( blockName ) => + WIDE_ALIGNMENTS.innerContainers.includes( blockName ); + +export const alignmentHelpers = { + isFullWidth, + isWideWidth, + isWider, + isContainerRelated, }; diff --git a/packages/components/src/mobile/utils/use-unit-converter-to-mobile.native.js b/packages/components/src/mobile/utils/use-unit-converter-to-mobile.native.js index ec34c1b7880217..cf8a77b8d0e561 100644 --- a/packages/components/src/mobile/utils/use-unit-converter-to-mobile.native.js +++ b/packages/components/src/mobile/utils/use-unit-converter-to-mobile.native.js @@ -35,7 +35,32 @@ const getValueAndUnit = ( value, unit ) => { return undefined; }; +const convertUnitToMobile = ( containerSize, globalStyles, value, unit ) => { + const { width, height } = containerSize; + const { valueToConvert, valueUnit } = getValueAndUnit( value, unit ); + const { fontSize = 16 } = globalStyles || {}; + + switch ( valueUnit ) { + case 'rem': + case 'em': + return valueToConvert * fontSize; + case '%': + return Number( valueToConvert / 100 ) * width; + case 'px': + return Number( valueToConvert ); + case 'vw': + const vw = width / 100; + return Math.round( valueToConvert * vw ); + case 'vh': + const vh = height / 100; + return Math.round( valueToConvert * vh ); + default: + return Number( valueToConvert / 100 ) * width; + } +}; + const useConvertUnitToMobile = ( value, unit ) => { + const { globalStyles: styles } = useContext( GlobalStylesContext ); const [ windowSizes, setWindowSizes ] = useState( Dimensions.get( 'window' ) ); @@ -47,36 +72,21 @@ const useConvertUnitToMobile = ( value, unit ) => { Dimensions.removeEventListener( 'change', onDimensionsChange ); }; }, [] ); - const { globalStyles: styles } = useContext( GlobalStylesContext ); const onDimensionsChange = useCallback( ( { window } ) => { setWindowSizes( window ); }, [] ); return useMemo( () => { - const { width, height } = windowSizes; - const { fontSize = 16 } = styles || {}; - const { valueToConvert, valueUnit } = getValueAndUnit( value, unit ); - switch ( valueUnit ) { - case 'rem': - case 'em': - return valueToConvert * fontSize; - case '%': - return Number( valueToConvert / 100 ) * width; - case 'px': - return Number( valueToConvert ); - case 'vw': - const vw = width / 100; - return Math.round( valueToConvert * vw ); - case 'vh': - const vh = height / 100; - return Math.round( valueToConvert * vh ); - default: - return Number( valueToConvert / 100 ) * width; - } + return convertUnitToMobile( + windowSizes, + styles, + valueToConvert, + valueUnit + ); }, [ windowSizes, value, unit ] ); }; -export { useConvertUnitToMobile, getValueAndUnit }; +export { convertUnitToMobile, useConvertUnitToMobile, getValueAndUnit }; diff --git a/packages/compose/src/hooks/use-resize-observer/index.native.js b/packages/compose/src/hooks/use-resize-observer/index.native.js index 26e6f647f5b5a7..477b4b77d993b0 100644 --- a/packages/compose/src/hooks/use-resize-observer/index.native.js +++ b/packages/compose/src/hooks/use-resize-observer/index.native.js @@ -39,7 +39,10 @@ const useResizeObserver = () => { prevState.width !== width || prevState.height !== height ) { - return { width, height }; + return { + width: Math.floor( width ), + height: Math.floor( height ), + }; } return prevState; } ); diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 5e8ccb56e967dd..be2d7594088176 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -11,6 +11,8 @@ For each user feature we should also add a importance categorization label to i ## Unreleased +* [***] Full-width and wide alignment support for Columns + ## 1.43.0 (2020-12-17) * [***] Adding support for selecting different unit of value in Cover and Columns blocks [#26161]