From 0c2ce6d672d3491b54a5fa93c86212ad34ace58e Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco Date: Wed, 3 Aug 2022 11:40:57 +0200 Subject: [PATCH] =?UTF-8?q?[Mobile]=C2=A0List=20V2=20Support=20(#42702)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Mobile - BlockList - Only render ListEmptyComponent at root level * Mobile - Register ListItem block * ListItem block - Export IdentUI to share it with Mobile * Mobile - Export useRefEffect * List V2 - Make it compatible with mobile by passing two props to their inner blocks and selecting a native TagName * Mobile - ListItem block - Add native variant * List V2 - Add style prop for mobile * Mobile - ListItem block - Add color customization support * Mobile - ListItem - Pass style instead of specific values to prevent other data like fontSize/lineHeight * Mobile - Add List V2 feature flag * [Mobile] - List V2 - List Style Type (#42703) * Mobile - List V2: - List Item - Add support for list type for both ordered and standard icon. - List - Avoid passing the start prop on Mobile * Mobile - ListStyleType - Fix default color * Mobile - ListItem - Add margin for parent list * Mobile - ListItem - Check if there's a default font size from the theme * Mobile - ListItem - Add deleteEnter for RichText * Mobile - ListItem - Remove unneeded theme fontSize parsing * Mobile - ListItem - Fix list margins * Mobile - ListItem - Fix margins * Mobile - Global styles - Parse font sizes * Mobile - List Style - Use theme.json font size as a default if its a block based theme * Mobile - Update CHANGELOG * Mobile - List Block V2 - Unit tests * Mobile - BlockList - Fix appender * Mobile - RichText - Add container width prop * Mobile - List V2 - Fix issue on Android where the font size / line-height updates weren't showing correctly due to issues with the views layout * List V2 - Move TagName to its own file with both web and native variant * Mobile - Update RichText snapshots --- .../src/components/block-list/index.native.js | 2 +- .../src/components/rich-text/index.native.js | 2 + .../test/__snapshots__/edit.native.js.snap | 18 +- .../test/__snapshots__/edit.native.js.snap | 36 +- packages/block-library/src/index.native.js | 17 +- packages/block-library/src/list-item/edit.js | 2 +- .../src/list-item/edit.native.js | 148 +++++ .../src/list-item/icons.native.js | 34 ++ .../src/list-item/list-style-type.native.js | 139 +++++ .../src/list-item/style.native.scss | 45 ++ packages/block-library/src/list/index.js | 2 +- .../test/__snapshots__/edit.native.js.snap | 133 +++++ .../src/list/test/edit.native.js | 518 +++++++++++++++++- packages/block-library/src/list/v2/edit.js | 16 +- .../block-library/src/list/v2/tag-name.js | 13 + .../src/list/v2/tag-name.native.js | 12 + .../test/__snapshots__/edit.native.js.snap | 18 +- .../test/__snapshots__/edit.native.js.snap | 63 ++- .../global-styles-context/utils.native.js | 21 +- packages/compose/src/index.native.js | 1 + packages/edit-post/src/test/editor.native.js | 4 +- .../mobile/WPAndroidGlue/GutenbergProps.kt | 3 + .../react-native-bridge/ios/Gutenberg.swift | 4 + .../ios/GutenbergBridgeDataSource.swift | 1 + packages/react-native-editor/CHANGELOG.md | 3 + packages/react-native-editor/src/setup.js | 11 +- .../rich-text/src/component/index.native.js | 16 +- .../test/__snapshots__/index.native.js.snap | 18 +- test/native/__mocks__/styleMock.js | 3 + 29 files changed, 1258 insertions(+), 45 deletions(-) create mode 100644 packages/block-library/src/list-item/edit.native.js create mode 100644 packages/block-library/src/list-item/icons.native.js create mode 100644 packages/block-library/src/list-item/list-style-type.native.js create mode 100644 packages/block-library/src/list-item/style.native.scss create mode 100644 packages/block-library/src/list/test/__snapshots__/edit.native.js.snap create mode 100644 packages/block-library/src/list/v2/tag-name.js create mode 100644 packages/block-library/src/list/v2/tag-name.native.js 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 e21f9855ab370..b562daedcf30c 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -469,7 +469,7 @@ class EmptyListComponent extends Component { renderFooterAppender, } = this.props; - if ( renderFooterAppender ) { + if ( renderFooterAppender || renderAppender === false ) { return null; } diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index 0232b68a2a671..2c21f63ee8c27 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -113,6 +113,7 @@ function RichTextWrapper( setRef, disableSuggestions, disableAutocorrection, + containerWidth, ...props }, forwardedRef @@ -639,6 +640,7 @@ function RichTextWrapper( setRef={ setRef } disableSuggestions={ disableSuggestions } disableAutocorrection={ disableAutocorrection } + containerWidth={ containerWidth } // Props to be set on the editable container are destructured on the // element itself for web (see below), but passed through rich text // for native. diff --git a/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap index 6607aa41ddcaf..4a72a64f314b4 100644 --- a/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap @@ -97,7 +97,14 @@ exports[`Audio block renders audio block error state without crashing 1`] = ` } } > - + - + - + - + - + - + ( !! __DEV__ ? block : null ); const iOSOnly = ( block ) => Platform.OS === 'ios' ? block : devOnly( block ); +// To be removed once List V2 is released on the web editor. +function listCheck( listBlock, blocksFlags ) { + if ( blocksFlags?.__experimentalEnableListBlockV2 ) { + listBlock.settings = listBlock?.settingsV2; + } + return listBlock; +} + // Hide the Classic block and SocialLink block addFilter( 'blocks.registerBlockType', @@ -230,9 +240,11 @@ addFilter( * * registerCoreBlocks(); * ``` + * @param {Object} [blocksFlags] Experimental flags + * * */ -export const registerCoreBlocks = () => { +export const registerCoreBlocks = ( blocksFlags ) => { // When adding new blocks to this list please also consider updating /src/block-support/supported-blocks.json in the Gutenberg-Mobile repo [ paragraph, @@ -244,7 +256,8 @@ export const registerCoreBlocks = () => { video, nextpage, separator, - list, + listCheck( list, blocksFlags ), + listItem, quote, mediaText, preformatted, diff --git a/packages/block-library/src/list-item/edit.js b/packages/block-library/src/list-item/edit.js index 303cade808454..3447cce9e4bbd 100644 --- a/packages/block-library/src/list-item/edit.js +++ b/packages/block-library/src/list-item/edit.js @@ -30,7 +30,7 @@ import { } from './hooks'; import { convertToListItems } from './utils'; -function IndentUI( { clientId } ) { +export function IndentUI( { clientId } ) { const [ canIndent, indentListItem ] = useIndentListItem( clientId ); const [ canOutdent, outdentListItem ] = useOutdentListItem( clientId ); diff --git a/packages/block-library/src/list-item/edit.native.js b/packages/block-library/src/list-item/edit.native.js new file mode 100644 index 0000000000000..2762c28c6024d --- /dev/null +++ b/packages/block-library/src/list-item/edit.native.js @@ -0,0 +1,148 @@ +/** + * External dependencies + */ +import { View } from 'react-native'; + +/** + * WordPress dependencies + */ +import { + RichText, + useBlockProps, + useInnerBlocksProps, + BlockControls, + store as blockEditorStore, +} from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import { useState, useCallback } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { useSplit, useMerge } from './hooks'; +import { convertToListItems } from './utils'; +import { IndentUI } from './edit.js'; +import styles from './style.scss'; +import ListStyleType from './list-style-type'; + +export default function ListItemEdit( { + attributes, + setAttributes, + onReplace, + clientId, + style, +} ) { + const [ contentWidth, setContentWidth ] = useState(); + const { placeholder, content } = attributes; + const { + blockIndex, + hasInnerBlocks, + indentationLevel, + numberOfListItems, + ordered, + reversed, + start, + } = useSelect( + ( select ) => { + const { + getBlockAttributes, + getBlockCount, + getBlockIndex, + getBlockParentsByBlockName, + getBlockRootClientId, + } = select( blockEditorStore ); + const currentIdentationLevel = getBlockParentsByBlockName( + clientId, + 'core/list-item', + true + ).length; + const currentBlockIndex = getBlockIndex( clientId ); + const blockWithInnerBlocks = getBlockCount( clientId ) > 0; + const rootClientId = getBlockRootClientId( clientId ); + const blockAttributes = getBlockAttributes( rootClientId ); + const totalListItems = getBlockCount( rootClientId ); + const { + ordered: isOrdered, + reversed: isReversed, + start: startValue, + } = blockAttributes || {}; + + return { + blockIndex: currentBlockIndex, + hasInnerBlocks: blockWithInnerBlocks, + indentationLevel: currentIdentationLevel, + numberOfListItems: totalListItems, + ordered: isOrdered, + reversed: isReversed, + start: startValue, + }; + }, + [ clientId ] + ); + + const blockProps = useBlockProps( { + ...( hasInnerBlocks && styles[ 'wp-block-list-item__nested-blocks' ] ), + } ); + const innerBlocksProps = useInnerBlocksProps( blockProps, { + allowedBlocks: [ 'core/list' ], + renderAppender: false, + } ); + + const onSplit = useSplit( clientId ); + const onMerge = useMerge( clientId ); + const onLayout = useCallback( ( { nativeEvent } ) => { + setContentWidth( ( prevState ) => { + const { width } = nativeEvent.layout; + + if ( ! prevState || prevState.width !== width ) { + return Math.floor( width ); + } + return prevState; + } ); + }, [] ); + + return ( + + + + + + + + setAttributes( { content: nextContent } ) + } + value={ content } + placeholder={ placeholder || __( 'List' ) } + onSplit={ onSplit } + onMerge={ onMerge } + onReplace={ ( blocks, ...args ) => { + onReplace( convertToListItems( blocks ), ...args ); + } } + style={ style } + deleteEnter={ true } + containerWidth={ contentWidth } + /> + + + + + + + + ); +} diff --git a/packages/block-library/src/list-item/icons.native.js b/packages/block-library/src/list-item/icons.native.js new file mode 100644 index 0000000000000..ee8aebada41ef --- /dev/null +++ b/packages/block-library/src/list-item/icons.native.js @@ -0,0 +1,34 @@ +/** + * WordPress dependencies + */ +import { SVG, Rect } from '@wordpress/components'; + +export const circle = ( size, color ) => ( + + + +); + +export const circleOutline = ( size, color ) => ( + + + +); + +export const square = ( size, color ) => ( + + + +); diff --git a/packages/block-library/src/list-item/list-style-type.native.js b/packages/block-library/src/list-item/list-style-type.native.js new file mode 100644 index 0000000000000..c3bd787ff2960 --- /dev/null +++ b/packages/block-library/src/list-item/list-style-type.native.js @@ -0,0 +1,139 @@ +/** + * External dependencies + */ +import { View, Text } from 'react-native'; + +/** + * WordPress dependencies + */ +import { Icon } from '@wordpress/components'; +import { Platform } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import styles from './style.scss'; +import { circle, circleOutline, square } from './icons'; + +const DEFAULT_ICON_SIZE = 6; + +function getListNumberIndex( start, blockIndex, reversed, numberOfListItems ) { + if ( start ) { + return reversed + ? numberOfListItems - 1 + start - blockIndex + : start + blockIndex; + } + + if ( reversed ) { + return numberOfListItems - blockIndex; + } + + return blockIndex + 1; +} + +function OrderedList( { + blockIndex, + color, + fontSize, + numberOfListItems, + reversed, + start, + style, +} ) { + const orderedStyles = [ + styles[ 'wp-block-list-item__list-item-container--ordered' ], + Platform.isIOS && + styles[ 'wp-block-list-item__list-item-ordered--default' ], + Platform.isIOS && + style?.fontSize && + styles[ 'wp-block-list-item__list-item-ordered--custom' ], + ]; + const numberStyle = [ { fontSize, color } ]; + + const currentIndex = getListNumberIndex( + start, + blockIndex, + reversed, + numberOfListItems + ); + + return ( + + { currentIndex }. + + ); +} + +function IconList( { fontSize, color, defaultFontSize, indentationLevel } ) { + const iconSize = parseInt( + ( fontSize * DEFAULT_ICON_SIZE ) / defaultFontSize, + 10 + ); + + let listIcon = circle( iconSize, color ); + if ( indentationLevel === 1 ) { + listIcon = circleOutline( iconSize, color ); + } else if ( indentationLevel > 1 ) { + listIcon = square( iconSize, color ); + } + + const listStyles = [ + styles[ 'wp-block-list-item__list-item-container' ], + { marginTop: fontSize / 2 }, + ]; + + return ( + + + + ); +} + +export default function ListStyleType( { + blockIndex, + indentationLevel, + numberOfListItems, + ordered, + reversed, + start, + style, +} ) { + let defaultFontSize = + styles[ 'wp-block-list-item__list-item--default' ].fontSize; + + if ( style?.baseColors?.typography?.fontSize ) { + defaultFontSize = parseInt( style.baseColors.typography.fontSize, 10 ); + } + + const fontSize = parseInt( + style?.fontSize ? style.fontSize : defaultFontSize, + 10 + ); + const defaultColor = style?.baseColors?.color?.text + ? style.baseColors.color.text + : styles[ 'wp-block-list-item__list-item--default' ].color; + const color = style?.color ? style.color : defaultColor; + + if ( ordered ) { + return ( + + ); + } + + return ( + + ); +} diff --git a/packages/block-library/src/list-item/style.native.scss b/packages/block-library/src/list-item/style.native.scss new file mode 100644 index 0000000000000..6b4f2dcc04356 --- /dev/null +++ b/packages/block-library/src/list-item/style.native.scss @@ -0,0 +1,45 @@ +.wp-block-list-item__list-item-parent { + margin-left: $solid-border-space; + margin-right: $solid-border-space; +} + +.wp-block-list-item__nested-blocks { + margin-top: 14; +} + +.wp-block-list-item__list-item { + flex: 1; + flex-direction: row; +} + +.wp-block-list-item__list-item-icon { + align-items: center; + flex-grow: 0; +} + +.wp-block-list-item__list-item-content { + flex-grow: 1; + flex-shrink: 1; + background-color: transparent; +} + +.wp-block-list-item__list-item--default { + color: $black; + font-size: $editor-font-size; +} + +.wp-block-list-item__list-item-ordered--default { + margin-top: 2; +} + +.wp-block-list-item__list-item-ordered--custom { + margin-top: 3; +} + +.wp-block-list-item__list-item-container--ordered { + margin-right: 3; +} + +.wp-block-list-item__list-item-container { + margin-right: 8; +} diff --git a/packages/block-library/src/list/index.js b/packages/block-library/src/list/index.js index bef6b6558df89..a401d75eb1458 100644 --- a/packages/block-library/src/list/index.js +++ b/packages/block-library/src/list/index.js @@ -15,7 +15,7 @@ import settingsV2 from './v2'; const { name } = metadata; -export { metadata, name }; +export { metadata, name, settingsV2 }; const settingsV1 = { icon, diff --git a/packages/block-library/src/list/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/list/test/__snapshots__/edit.native.js.snap new file mode 100644 index 0000000000000..3fe53069b1775 --- /dev/null +++ b/packages/block-library/src/list/test/__snapshots__/edit.native.js.snap @@ -0,0 +1,133 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`List V2 block adds one item to the list 1`] = ` +" +
    +
  • First list item
  • +
+" +`; + +exports[`List V2 block changes the indentation level 1`] = ` +" +
    +
  • Item 1 +
      +
    • Item 2
    • +
    +
  • +
+" +`; + +exports[`List V2 block changes to ordered list 1`] = ` +" +
    +
  1. Item 1
  2. + + + +
  3. Item 2
  4. + + + +
  5. Item 3
  6. +
+" +`; + +exports[`List V2 block changes to reverse ordered list 1`] = ` +" +
    +
  1. Item 1
  2. + + + +
  3. Item 2
  4. + + + +
  5. Item 3
  6. +
+" +`; + +exports[`List V2 block inserts block 1`] = ` +" +
    +
  • +
+" +`; + +exports[`List V2 block removes the indentation level 1`] = ` +" +
    +
  • Item 1
  • + + + +
  • Item 2
  • +
+" +`; + +exports[`List V2 block sets a start value to an ordered list 1`] = ` +" +
    +
  1. Item 1
  2. + + + +
  3. Item 2
  4. + + + +
  5. Item 3
  6. +
+" +`; + +exports[`List V2 block shows different indentation levels 1`] = ` +" +
    +
  • List item 1
  • + + + +
  • List item 2 +
      +
    • List item nested 1
    • + + + +
    • List item nested 2 +
        +
      • Extra item 1
      • + + + +
      • Extra item 2
      • +
      +
    • +
    +
  • + + + +
  • List item 3
  • +
+" +`; + +exports[`List block inserts block 1`] = ` +" +
+" +`; + +exports[`List block renders a list with a few items 1`] = ` +" +
  • Item 1
  • Item 2
  • Item 3
+" +`; diff --git a/packages/block-library/src/list/test/edit.native.js b/packages/block-library/src/list/test/edit.native.js index 73a15b9604e4d..62738da807dc4 100644 --- a/packages/block-library/src/list/test/edit.native.js +++ b/packages/block-library/src/list/test/edit.native.js @@ -1,16 +1,520 @@ /** * External dependencies */ -import { render } from 'test/helpers'; +import { + changeTextOfRichText, + fireEvent, + getEditorHtml, + initializeEditor, + waitFor, + within, +} from 'test/helpers'; /** - * Internal dependencies + * WordPress dependencies */ -import ListEdit from '../edit'; +import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; +import { registerCoreBlocks } from '@wordpress/block-library'; -describe( 'ListEdit component', () => { - it( 'renders without crashing', () => { - const screen = render( ); - expect( screen.container ).toBeTruthy(); +describe( 'List block', () => { + beforeAll( () => { + // Register all core blocks + registerCoreBlocks( { + __experimentalEnableListBlockV2: false, + } ); + } ); + + afterAll( () => { + // Clean up registered blocks + getBlockTypes().forEach( ( block ) => { + unregisterBlockType( block.name ); + } ); + } ); + + it( 'inserts block', async () => { + const { getByA11yLabel, getByTestId, getByText } = + await initializeEditor(); + + fireEvent.press( getByA11yLabel( 'Add block' ) ); + + const blockList = getByTestId( 'InserterUI-Blocks' ); + // onScroll event used to force the FlatList to render all items + fireEvent.scroll( blockList, { + nativeEvent: { + contentOffset: { y: 0, x: 0 }, + contentSize: { width: 100, height: 100 }, + layoutMeasurement: { width: 100, height: 100 }, + }, + } ); + + fireEvent.press( await waitFor( () => getByText( 'List' ) ) ); + + expect( getByA11yLabel( /List Block\. Row 1/ ) ).toBeVisible(); + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'renders a list with a few items', async () => { + const initialHtml = ` +
  • Item 1
  • Item 2
  • Item 3
+ `; + + const { getByA11yLabel } = await initializeEditor( { + initialHtml, + } ); + + // Select List block + const listBlock = getByA11yLabel( /List Block\. Row 1/ ); + fireEvent.press( listBlock ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); +} ); + +describe( 'List V2 block', () => { + beforeAll( () => { + // Register all core blocks + registerCoreBlocks( { + __experimentalEnableListBlockV2: true, + } ); + } ); + + afterAll( () => { + // Clean up registered blocks + getBlockTypes().forEach( ( block ) => { + unregisterBlockType( block.name ); + } ); + } ); + + it( 'inserts block', async () => { + const { getByA11yLabel, getByTestId, getByText } = + await initializeEditor(); + + fireEvent.press( getByA11yLabel( 'Add block' ) ); + + const blockList = getByTestId( 'InserterUI-Blocks' ); + // onScroll event used to force the FlatList to render all items + fireEvent.scroll( blockList, { + nativeEvent: { + contentOffset: { y: 0, x: 0 }, + contentSize: { width: 100, height: 100 }, + layoutMeasurement: { width: 100, height: 100 }, + }, + } ); + + fireEvent.press( await waitFor( () => getByText( 'List' ) ) ); + + expect( getByA11yLabel( /List Block\. Row 1/ ) ).toBeVisible(); + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'adds one item to the list', async () => { + const initialHtml = ` +
    +
+ `; + + const { getByA11yLabel } = await initializeEditor( { + initialHtml, + } ); + + // Select List block + const listBlock = getByA11yLabel( /List Block\. Row 1/ ); + + fireEvent( + within( listBlock ).getByTestId( 'block-list-wrapper' ), + 'layout', + { + nativeEvent: { + layout: { + width: 100, + height: 50, + }, + }, + } + ); + + fireEvent.press( listBlock ); + + // Select List Item block + const listItemBlock = getByA11yLabel( /List item Block\. Row 1/ ); + fireEvent.press( listItemBlock ); + + const listItemField = + within( listBlock ).getByPlaceholderText( 'List' ); + changeTextOfRichText( listItemField, 'First list item' ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'shows different indentation levels', async () => { + const initialHtml = ` +
    +
  • List item 1
  • + + +
  • List item 2 +
      +
    • List item nested 1
    • + + +
    • List item nested 2 +
        +
      • Extra item 1
      • + + +
      • Extra item 2
      • +
      +
    • +
    +
  • + + +
  • List item 3
  • +
+ `; + + const { getByA11yLabel } = await initializeEditor( { + initialHtml, + } ); + + // Select List block + const listBlock = getByA11yLabel( /List Block\. Row 1/ ); + + fireEvent( + within( listBlock ).getByTestId( 'block-list-wrapper' ), + 'layout', + { + nativeEvent: { + layout: { + width: 100, + height: 50, + }, + }, + } + ); + + fireEvent.press( listBlock ); + + // Select List Item block + const firstNestedLevelBlock = within( listBlock ).getByA11yLabel( + /List item Block\. Row 2/ + ); + + fireEvent( + within( firstNestedLevelBlock ).getByTestId( 'block-list-wrapper' ), + 'layout', + { + nativeEvent: { + layout: { + width: 100, + height: 350, + }, + }, + } + ); + + fireEvent.press( firstNestedLevelBlock ); + + // Select second level list + const secondNestedLevelBlock = within( + firstNestedLevelBlock + ).getByA11yLabel( /List Block\. Row 1/ ); + + fireEvent( + within( secondNestedLevelBlock ).getByTestId( + 'block-list-wrapper' + ), + 'layout', + { + nativeEvent: { + layout: { + width: 100, + height: 50, + }, + }, + } + ); + + fireEvent.press( secondNestedLevelBlock ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'changes the indentation level', async () => { + const initialHtml = ` +
    +
  • Item 1
  • + + +
  • Item 2
  • +
+ `; + + const { getByA11yLabel } = await initializeEditor( { + initialHtml, + } ); + + // Select List block + const listBlock = getByA11yLabel( /List Block\. Row 1/ ); + + fireEvent( + within( listBlock ).getByTestId( 'block-list-wrapper' ), + 'layout', + { + nativeEvent: { + layout: { + width: 100, + height: 50, + }, + }, + } + ); + + fireEvent.press( listBlock ); + + // Select Secont List Item block + const listItemBlock = getByA11yLabel( /List item Block\. Row 2/ ); + fireEvent.press( listItemBlock ); + + // Update indentation + const indentButton = getByA11yLabel( 'Indent' ); + fireEvent.press( indentButton ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'removes the indentation level', async () => { + const initialHtml = ` +
    +
  • Item 1 +
      +
    • Item 2
    • +
    +
  • +
+ `; + + const { getByA11yLabel } = await initializeEditor( { + initialHtml, + } ); + + // Select List block + const listBlock = getByA11yLabel( /List Block\. Row 1/ ); + + fireEvent( + within( listBlock ).getByTestId( 'block-list-wrapper' ), + 'layout', + { + nativeEvent: { + layout: { + width: 100, + height: 50, + }, + }, + } + ); + + fireEvent.press( listBlock ); + + // Select List Item block + const firstNestedLevelBlock = within( listBlock ).getByA11yLabel( + /List item Block\. Row 1/ + ); + + fireEvent( + within( firstNestedLevelBlock ).getByTestId( 'block-list-wrapper' ), + 'layout', + { + nativeEvent: { + layout: { + width: 100, + height: 350, + }, + }, + } + ); + + fireEvent.press( firstNestedLevelBlock ); + + // Select Inner block List + const innerBlockList = within( firstNestedLevelBlock ).getByA11yLabel( + /List Block\. Row 1/ + ); + + fireEvent( + within( innerBlockList ).getByTestId( 'block-list-wrapper' ), + 'layout', + { + nativeEvent: { + layout: { + width: 100, + height: 50, + }, + }, + } + ); + + // Select nested List Item block + const listItemBlock = within( innerBlockList ).getByA11yLabel( + /List item Block\. Row 1/ + ); + fireEvent.press( listItemBlock ); + + // Update indentation + const outdentButton = getByA11yLabel( 'Outdent' ); + fireEvent.press( outdentButton ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'changes to ordered list', async () => { + const initialHtml = ` +
    +
  • Item 1
  • + + +
  • Item 2
  • + + +
  • Item 3
  • +
+ `; + + const { getByA11yLabel } = await initializeEditor( { + initialHtml, + } ); + + // Select List block + const listBlock = getByA11yLabel( /List Block\. Row 1/ ); + + fireEvent( + within( listBlock ).getByTestId( 'block-list-wrapper' ), + 'layout', + { + nativeEvent: { + layout: { + width: 100, + height: 50, + }, + }, + } + ); + + fireEvent.press( listBlock ); + + // Update to ordered list + const orderedButton = getByA11yLabel( 'Ordered' ); + fireEvent.press( orderedButton ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'changes to reverse ordered list', async () => { + const initialHtml = ` +
    +
  • Item 1
  • + + +
  • Item 2
  • + + +
  • Item 3
  • +
+ `; + + const { getByA11yLabel, getByTestId } = await initializeEditor( { + initialHtml, + } ); + + // Select List block + const listBlock = getByA11yLabel( /List Block\. Row 1/ ); + + fireEvent( + within( listBlock ).getByTestId( 'block-list-wrapper' ), + 'layout', + { + nativeEvent: { + layout: { + width: 100, + height: 50, + }, + }, + } + ); + + fireEvent.press( listBlock ); + + // Update to ordered list + const orderedButton = getByA11yLabel( 'Ordered' ); + fireEvent.press( orderedButton ); + + // Set order to reverse + + // Open block settings + fireEvent.press( getByA11yLabel( 'Open Settings' ) ); + await waitFor( + () => getByTestId( 'block-settings-modal' ).props.isVisible + ); + + const reverseButton = getByA11yLabel( /Reverse list numbering\. Off/ ); + fireEvent.press( reverseButton ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'sets a start value to an ordered list', async () => { + const initialHtml = ` +
    +
  • Item 1
  • + + +
  • Item 2
  • + + +
  • Item 3
  • +
+ `; + + const { getByA11yLabel, getByTestId } = await initializeEditor( { + initialHtml, + } ); + + // Select List block + const listBlock = getByA11yLabel( /List Block\. Row 1/ ); + + fireEvent( + within( listBlock ).getByTestId( 'block-list-wrapper' ), + 'layout', + { + nativeEvent: { + layout: { + width: 100, + height: 50, + }, + }, + } + ); + + fireEvent.press( listBlock ); + + // Update to ordered list + const orderedButton = getByA11yLabel( 'Ordered' ); + fireEvent.press( orderedButton ); + + // Set order to reverse + + // Open block settings + fireEvent.press( getByA11yLabel( 'Open Settings' ) ); + await waitFor( + () => getByTestId( 'block-settings-modal' ).props.isVisible + ); + + const startValueButton = getByA11yLabel( /Start value\. Empty/ ); + fireEvent.press( startValueButton ); + const startValueInput = + within( startValueButton ).getByDisplayValue( '' ); + fireEvent.changeText( startValueInput, '25' ); + + expect( getEditorHtml() ).toMatchSnapshot(); } ); } ); diff --git a/packages/block-library/src/list/v2/edit.js b/packages/block-library/src/list/v2/edit.js index 77029179d2879..9ef320b53b001 100644 --- a/packages/block-library/src/list/v2/edit.js +++ b/packages/block-library/src/list/v2/edit.js @@ -24,7 +24,7 @@ import { formatOutdentRTL, } from '@wordpress/icons'; import { createBlock } from '@wordpress/blocks'; -import { useCallback, useEffect } from '@wordpress/element'; +import { useCallback, useEffect, Platform } from '@wordpress/element'; import deprecated from '@wordpress/deprecated'; /** @@ -32,8 +32,10 @@ import deprecated from '@wordpress/deprecated'; */ import OrderedListSettings from '../ordered-list-settings'; import { migrateToListV2 } from './migrate'; +import TagName from './tag-name'; const TEMPLATE = [ [ 'core/list-item' ] ]; +const NATIVE_MARGIN_SPACING = 8; /** * At the moment, deprecations don't handle create blocks from attributes @@ -126,16 +128,21 @@ function IndentUI( { clientId } ) { ); } -function Edit( { attributes, setAttributes, clientId } ) { - const blockProps = useBlockProps(); +function Edit( { attributes, setAttributes, clientId, style } ) { + const blockProps = useBlockProps( { + ...( Platform.isNative && { style } ), + } ); const innerBlocksProps = useInnerBlocksProps( blockProps, { allowedBlocks: [ 'core/list-item' ], template: TEMPLATE, templateInsertUpdatesSelection: true, + ...( Platform.isNative && { + marginVertical: NATIVE_MARGIN_SPACING, + marginHorizontal: NATIVE_MARGIN_SPACING, + } ), } ); useMigrateOnLoad( attributes, clientId ); const { ordered, reversed, start } = attributes; - const TagName = ordered ? 'ol' : 'ul'; const controls = ( @@ -164,6 +171,7 @@ function Edit( { attributes, setAttributes, clientId } ) { return ( <> ; +} + +export default forwardRef( TagName ); diff --git a/packages/block-library/src/list/v2/tag-name.native.js b/packages/block-library/src/list/v2/tag-name.native.js new file mode 100644 index 0000000000000..ddf9987bfa515 --- /dev/null +++ b/packages/block-library/src/list/v2/tag-name.native.js @@ -0,0 +1,12 @@ +/** + * WordPress dependencies + */ +import { forwardRef } from '@wordpress/element'; +import { View } from '@wordpress/primitives'; + +function TagName( props, ref ) { + const { start, ...extraProps } = props; + return ; +} + +export default forwardRef( TagName ); diff --git a/packages/block-library/src/preformatted/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/preformatted/test/__snapshots__/edit.native.js.snap index 9868476671f5a..08bbbd42e7dc7 100644 --- a/packages/block-library/src/preformatted/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/preformatted/test/__snapshots__/edit.native.js.snap @@ -10,7 +10,14 @@ exports[`core/more/edit/native should match snapshot when content is empty 1`] = ] } > - + - + - + - + - +