From a68cbf6a3dd9a7928251db2b8a269fd105dbaffa Mon Sep 17 00:00:00 2001 From: arthur791004 Date: Mon, 19 Apr 2021 15:56:36 +0800 Subject: [PATCH 01/20] Docs: Add fix to suggested prefixes of branch (#30953) --- docs/contributors/code/git-workflow.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/contributors/code/git-workflow.md b/docs/contributors/code/git-workflow.md index a7a2d8f4833f2..16db71ffc7127 100644 --- a/docs/contributors/code/git-workflow.md +++ b/docs/contributors/code/git-workflow.md @@ -83,6 +83,7 @@ Suggested prefixes: - `try/` = experimental feature, "tentatively add" - `update/` = update an existing feature - `remove/` = remove an existing feature +- `fix/` = fix an existing issue For example, `add/gallery-block` means you're working on adding a new gallery block. From bc9227812b82f899c03945fb289611e786f22a0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= Date: Mon, 19 Apr 2021 10:20:24 +0200 Subject: [PATCH 02/20] Block Editor: Standardize loading default block editor settings (#30245) * Block Editor: Standardize loading default block editor settings * Ensure that allowed_block_types is executed only when the post if defined * Sync block editor shared methods with Wordpress core --- lib/block-editor.php | 293 +++++++++++++++++++++ lib/editor-settings.php | 69 ----- lib/full-site-editing/edit-site-page.php | 3 +- lib/global-styles.php | 2 +- lib/load.php | 1 + lib/navigation-page.php | 2 +- lib/widgets-page.php | 2 +- phpunit/block-editor.php | 315 +++++++++++++++++++++++ phpunit/class-wp-theme-json-test.php | 6 +- 9 files changed, 616 insertions(+), 77 deletions(-) create mode 100644 lib/block-editor.php create mode 100644 phpunit/block-editor.php diff --git a/lib/block-editor.php b/lib/block-editor.php new file mode 100644 index 0000000000000..81603276d27b1 --- /dev/null +++ b/lib/block-editor.php @@ -0,0 +1,293 @@ + 'text', + 'title' => _x( 'Text', 'block category' ), + 'icon' => null, + ), + array( + 'slug' => 'media', + 'title' => _x( 'Media', 'block category' ), + 'icon' => null, + ), + array( + 'slug' => 'design', + 'title' => _x( 'Design', 'block category' ), + 'icon' => null, + ), + array( + 'slug' => 'widgets', + 'title' => _x( 'Widgets', 'block category' ), + 'icon' => null, + ), + array( + 'slug' => 'theme', + 'title' => _x( 'Theme', 'block category' ), + 'icon' => null, + ), + array( + 'slug' => 'embed', + 'title' => _x( 'Embeds', 'block category' ), + 'icon' => null, + ), + array( + 'slug' => 'reusable', + 'title' => _x( 'Reusable Blocks', 'block category' ), + 'icon' => null, + ), + ); +} + +/** + * Returns all the categories for block types that will be shown in the block editor. + * + * This is a temporary solution until the Gutenberg plugin sets + * the required WordPress version to 5.8. + * + * @see https://core.trac.wordpress.org/ticket/52920 + * + * @since 10.5.0 + * + * @param string|WP_Post $editor_name_or_post The name of the editor (e.g. 'post-editor') + * or the post object. + * + * @return array[] Array of categories for block types. + */ +function gutenberg_get_block_categories( $editor_name_or_post ) { + // Assume the post editor when the WP_Post object passed. + $editor_name = is_object( $editor_name_or_post ) ? 'post-editor' : $editor_name_or_post; + $default_categories = get_default_block_categories(); + + /** + * Filters the default array of categories for block types. + * + * @since 5.8.0 + * + * @param array[] $default_categories Array of categories for block types. + */ + $block_categories = apply_filters( "block_categories_{$editor_name}", $default_categories ); + if ( 'post-editor' === $editor_name ) { + $post = is_object( $editor_name_or_post ) ? $editor_name_or_post : get_post(); + + /** + * Filters the default array of categories for block types. + * + * @since 5.0.0 + * @deprecated 5.8.0 The hook transitioned to support also screens that don't contain the $post instance. + * + * @param array[] $block_categories Array of categories for block types. + * @param WP_Post $post Post being loaded. + */ + $block_categories = apply_filters_deprecated( 'block_categories', array( $block_categories, $post ), '5.8.0', "block_categories_{$editor_name}" ); + } + + return $block_categories; +} + +/** + * Gets the list of allowed block types to use in the block editor. + * + * This is a temporary solution until the Gutenberg plugin sets + * the required WordPress version to 5.8. + * + * @see https://core.trac.wordpress.org/ticket/52920 + * + * @since 10.5.0 + * + * @param string $editor_name The name of the editor (e.g. 'post-editor'). + * + * @return bool|array Array of block type slugs, or boolean to enable/disable all. + */ +function gutenberg_get_allowed_block_types( $editor_name ) { + $allowed_block_types = true; + + /** + * Filters the allowed block types for the given editor, defaulting to true (all + * registered block types supported). + * + * @since 5.8.0 + * + * @param bool|array $allowed_block_types Array of block type slugs, or + * boolean to enable/disable all. + */ + $allowed_block_types = apply_filters( "allowed_block_types_{$editor_name}", $allowed_block_types ); + if ( 'post-editor' === $editor_name ) { + $post = get_post(); + + /** + * Filters the allowed block types for the editor, defaulting to true (all + * block types supported). + * + * @since 5.0.0 + * @deprecated 5.8.0 The hook transitioned to support also screens that don't contain $post instance. + * + * @param bool|array $allowed_block_types Array of block type slugs, or + * boolean to enable/disable all. + * @param WP_Post $post The post resource data. + */ + $allowed_block_types = apply_filters_deprecated( 'allowed_block_types', array( $allowed_block_types, $post ), '5.8.0', "allowed_block_types_{$editor_name}" ); + } + + return $allowed_block_types; +} + +/** + * Returns the default block editor settings. + * + * This is a temporary solution until the Gutenberg plugin sets + * the required WordPress version to 5.8. + * + * @see https://core.trac.wordpress.org/ticket/52920 + * + * @since 10.5.0 + * + * @return array The default block editor settings. + */ +function gutenberg_get_default_block_editor_settings() { + // Media settings. + $max_upload_size = wp_max_upload_size(); + if ( ! $max_upload_size ) { + $max_upload_size = 0; + } + + /** This filter is documented in wp-admin/includes/media.php */ + $image_size_names = apply_filters( + 'image_size_names_choose', + array( + 'thumbnail' => __( 'Thumbnail' ), + 'medium' => __( 'Medium' ), + 'large' => __( 'Large' ), + 'full' => __( 'Full Size' ), + ) + ); + + $available_image_sizes = array(); + foreach ( $image_size_names as $image_size_slug => $image_size_name ) { + $available_image_sizes[] = array( + 'slug' => $image_size_slug, + 'name' => $image_size_name, + ); + } + + $default_size = get_option( 'image_default_size', 'large' ); + $image_default_size = in_array( $default_size, array_keys( $image_size_names ), true ) ? $image_default_size : 'large'; + + $image_dimensions = array(); + $all_sizes = wp_get_registered_image_subsizes(); + foreach ( $available_image_sizes as $size ) { + $key = $size['slug']; + if ( isset( $all_sizes[ $key ] ) ) { + $image_dimensions[ $key ] = $all_sizes[ $key ]; + } + } + + $editor_settings = array( + '__unstableEnableFullSiteEditingBlocks' => gutenberg_supports_block_templates(), + 'alignWide' => get_theme_support( 'align-wide' ), + 'allowedBlockTypes' => true, + 'allowedMimeTypes' => get_allowed_mime_types(), + 'blockCategories' => gutenberg_get_default_block_categories(), + 'disableCustomColors' => get_theme_support( 'disable-custom-colors' ), + 'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ), + 'disableCustomGradients' => get_theme_support( 'disable-custom-gradients' ), + 'enableCustomLineHeight' => get_theme_support( 'custom-line-height' ), + 'enableCustomSpacing' => get_theme_support( 'custom-spacing' ), + 'enableCustomUnits' => get_theme_support( 'custom-units' ), + 'isRTL' => is_rtl(), + 'imageDefaultSize' => $image_default_size, + 'imageDimensions' => $image_dimensions, + 'imageEditing' => true, + 'imageSizes' => $available_image_sizes, + 'maxUploadFileSize' => $max_upload_size, + ); + + // Theme settings. + $color_palette = current( (array) get_theme_support( 'editor-color-palette' ) ); + if ( false !== $color_palette ) { + $editor_settings['colors'] = $color_palette; + } + + $font_sizes = current( (array) get_theme_support( 'editor-font-sizes' ) ); + if ( false !== $font_sizes ) { + $editor_settings['fontSizes'] = $font_sizes; + } + + $gradient_presets = current( (array) get_theme_support( 'editor-gradient-presets' ) ); + if ( false !== $gradient_presets ) { + $editor_settings['gradients'] = $gradient_presets; + } + + return $editor_settings; +} + +/** + * Returns the contextualized block editor settings settings for a selected editor type. + * + * This is a temporary solution until the Gutenberg plugin sets + * the required WordPress version to 5.8. + * + * @see https://core.trac.wordpress.org/ticket/52920 + * + * @since 10.5.0 + * + * @param string $editor_name The name of the editor (e.g. 'post-editor'). + * @param array $custom_settings Optional custom settings to use with the editor type. + * + * @return array The contextualized block editor settings. + */ +function gutenberg_get_block_editor_settings( $editor_name, $custom_settings = array() ) { + $editor_settings = array_merge( + gutenberg_get_default_block_editor_settings( $editor_name ), + array( + 'allowedBlockTypes' => gutenberg_get_allowed_block_types( $editor_name ), + 'blockCategories' => gutenberg_get_block_categories( $editor_name ), + ), + $custom_settings + ); + + /** + * Filters the settings to pass to the block editor for a given editor type. + * + * @since 5.8.0 + * + * @param array $editor_settings Default editor settings. + */ + $editor_settings = apply_filters( "block_editor_settings_{$editor_name}", $editor_settings ); + if ( 'post-editor' === $editor_name ) { + $post = get_post(); + + /** + * Filters the settings to pass to the block editor. + * + * @since 5.0.0 + * @deprecated 5.8.0 The hook transitioned to support also screens that don't contain $post instance. + * + * @param array $editor_settings Default editor settings. + * @param WP_Post $post Post being edited. + */ + $editor_settings = apply_filters_deprecated( 'block_editor_settings', array( $editor_settings, $post ), '5.8.0', "block_editor_settings_{$editor_name}" ); + } + + return $editor_settings; +} diff --git a/lib/editor-settings.php b/lib/editor-settings.php index 73912d63e028b..0c8ed73d29c38 100644 --- a/lib/editor-settings.php +++ b/lib/editor-settings.php @@ -5,71 +5,6 @@ * @package gutenberg */ -/** - * Returns editor settings that are common to all editors: - * post, site, widgets, and navigation. - * - * All these settings are already part of core, - * see edit-form-blocks.php - * - * @return array Common editor settings. - */ -function gutenberg_get_common_block_editor_settings() { - $max_upload_size = wp_max_upload_size(); - if ( ! $max_upload_size ) { - $max_upload_size = 0; - } - - $available_image_sizes = array(); - // This filter is documented in wp-admin/includes/media.php. - $image_size_names = apply_filters( - 'image_size_names_choose', - array( - 'thumbnail' => __( 'Thumbnail', 'gutenberg' ), - 'medium' => __( 'Medium', 'gutenberg' ), - 'large' => __( 'Large', 'gutenberg' ), - 'full' => __( 'Full Size', 'gutenberg' ), - ) - ); - foreach ( $image_size_names as $image_size_slug => $image_size_name ) { - $available_image_sizes[] = array( - 'slug' => $image_size_slug, - 'name' => $image_size_name, - ); - } - - $settings = array( - '__unstableEnableFullSiteEditingBlocks' => gutenberg_supports_block_templates(), - 'disableCustomColors' => get_theme_support( 'disable-custom-colors' ), - 'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ), - 'disableCustomGradients' => get_theme_support( 'disable-custom-gradients' ), - 'enableCustomLineHeight' => get_theme_support( 'custom-line-height' ), - 'enableCustomUnits' => get_theme_support( 'custom-units' ), - 'enableCustomSpacing' => get_theme_support( 'custom-spacing' ), - 'imageSizes' => $available_image_sizes, - 'isRTL' => is_rtl(), - 'maxUploadFileSize' => $max_upload_size, - 'allowedMimeTypes' => get_allowed_mime_types(), - ); - - $color_palette = current( (array) get_theme_support( 'editor-color-palette' ) ); - if ( false !== $color_palette ) { - $settings['colors'] = $color_palette; - } - - $font_sizes = current( (array) get_theme_support( 'editor-font-sizes' ) ); - if ( false !== $font_sizes ) { - $settings['fontSizes'] = $font_sizes; - } - - $gradient_presets = current( (array) get_theme_support( 'editor-gradient-presets' ) ); - if ( false !== $gradient_presets ) { - $settings['gradients'] = $gradient_presets; - } - - return $settings; -} - /** * Extends the block editor with settings that are only in the plugin. * @@ -78,10 +13,6 @@ function gutenberg_get_common_block_editor_settings() { * @return array Filtered settings. */ function gutenberg_extend_post_editor_settings( $settings ) { - $image_default_size = get_option( 'image_default_size', 'large' ); - $image_sizes = wp_list_pluck( $settings['imageSizes'], 'slug' ); - - $settings['imageDefaultSize'] = in_array( $image_default_size, $image_sizes, true ) ? $image_default_size : 'large'; $settings['__unstableEnableFullSiteEditingBlocks'] = gutenberg_supports_block_templates(); return $settings; diff --git a/lib/full-site-editing/edit-site-page.php b/lib/full-site-editing/edit-site-page.php index 783491faa5e74..8d9f09878cce8 100644 --- a/lib/full-site-editing/edit-site-page.php +++ b/lib/full-site-editing/edit-site-page.php @@ -90,9 +90,8 @@ function gutenberg_edit_site_init( $hook ) { $current_screen->is_block_editor( true ); $settings = array_merge( - gutenberg_get_common_block_editor_settings(), + gutenberg_get_default_block_editor_settings(), array( - 'alignWide' => get_theme_support( 'align-wide' ), 'siteUrl' => site_url(), 'postsPerPage' => get_option( 'posts_per_page' ), 'styles' => gutenberg_get_editor_styles(), diff --git a/lib/global-styles.php b/lib/global-styles.php index c3e7a6da9cfcf..de843af00fee5 100644 --- a/lib/global-styles.php +++ b/lib/global-styles.php @@ -63,7 +63,7 @@ function gutenberg_experimental_global_styles_enqueue_assets() { return; } - $settings = gutenberg_get_common_block_editor_settings(); + $settings = gutenberg_get_default_block_editor_settings(); $theme_support_data = WP_Theme_JSON::get_from_editor_settings( $settings ); $all = WP_Theme_JSON_Resolver::get_merged_data( $theme_support_data ); diff --git a/lib/load.php b/lib/load.php index 2deda54b6646b..3c817423d71c8 100644 --- a/lib/load.php +++ b/lib/load.php @@ -106,6 +106,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/full-site-editing/edit-site-export.php'; require __DIR__ . '/blocks.php'; +require __DIR__ . '/block-editor.php'; require __DIR__ . '/block-patterns.php'; require __DIR__ . '/client-assets.php'; require __DIR__ . '/demo.php'; diff --git a/lib/navigation-page.php b/lib/navigation-page.php index 1837245544c53..795e7d93baf2a 100644 --- a/lib/navigation-page.php +++ b/lib/navigation-page.php @@ -33,7 +33,7 @@ function gutenberg_navigation_init( $hook ) { } $settings = array_merge( - gutenberg_get_common_block_editor_settings(), + gutenberg_get_default_block_editor_settings(), array( 'blockNavMenus' => get_theme_support( 'block-nav-menus' ), ) diff --git a/lib/widgets-page.php b/lib/widgets-page.php index 5e95295a7d04a..891153bcd72b2 100644 --- a/lib/widgets-page.php +++ b/lib/widgets-page.php @@ -42,7 +42,7 @@ function gutenberg_widgets_init( $hook ) { } $settings = array_merge( - gutenberg_get_common_block_editor_settings(), + gutenberg_get_default_block_editor_settings(), gutenberg_get_legacy_widget_settings() ); diff --git a/phpunit/block-editor.php b/phpunit/block-editor.php new file mode 100644 index 0000000000000..cf30a961f0fbc --- /dev/null +++ b/phpunit/block-editor.php @@ -0,0 +1,315 @@ + 'Example', + ); + + $post = $this->factory()->post->create_and_get( $args ); + } + + function filter_set_block_categories_post( $block_categories, $post ) { + if ( empty( $post ) ) { + return $block_categories; + } + + return array( + array( + 'slug' => 'filtered-category', + 'title' => 'Filtered Category', + 'icon' => null, + ), + ); + } + + function filter_set_allowed_block_types_post( $allowed_block_types, $post ) { + if ( empty( $post ) ) { + return $allowed_block_types; + } + + return array( 'test/filtered-block' ); + } + + function filter_set_block_editor_settings_post( $editor_settings, $post ) { + if ( empty( $post ) ) { + return $editor_settings; + } + + return array( + 'filter' => 'deprecated', + ); + } + + /** + * @ticket 52920 + * @expectedDeprecated block_categories + */ + function test_get_block_categories_deprecated_filter_post_object() { + add_filter( 'block_categories', array( $this, 'filter_set_block_categories_post' ), 10, 2 ); + + $block_categories = gutenberg_get_block_categories( get_post() ); + + remove_filter( 'block_categories', array( $this, 'filter_set_block_categories_post' ) ); + + $this->assertSameSets( + array( + array( + 'slug' => 'filtered-category', + 'title' => 'Filtered Category', + 'icon' => null, + ), + ), + $block_categories + ); + } + + /** + * @ticket 52920 + * @expectedDeprecated block_categories + */ + function test_get_block_categories_deprecated_filter_post_editor() { + add_filter( 'block_categories', array( $this, 'filter_set_block_categories_post' ), 10, 2 ); + + $block_categories = gutenberg_get_block_categories( 'post-editor' ); + + remove_filter( 'block_categories', array( $this, 'filter_set_block_categories_post' ) ); + + $this->assertSameSets( + array( + array( + 'slug' => 'filtered-category', + 'title' => 'Filtered Category', + 'icon' => null, + ), + ), + $block_categories + ); + } + + /** + * @ticket 52920 + */ + function test_get_allowed_block_types_default() { + $allowed_block_types = gutenberg_get_allowed_block_types( 'post-editor' ); + + $this->assertTrue( $allowed_block_types ); + } + + /** + * @ticket 52920 + * @expectedDeprecated allowed_block_types + */ + function test_get_allowed_block_types_deprecated_filter_post_editor() { + add_filter( 'allowed_block_types', array( $this, 'filter_set_allowed_block_types_post' ), 10, 2 ); + + $allowed_block_types = gutenberg_get_allowed_block_types( 'post-editor' ); + + remove_filter( 'allowed_block_types', array( $this, 'filter_set_allowed_block_types_post' ) ); + + $this->assertSameSets( array( 'test/filtered-block' ), $allowed_block_types ); + } + + /** + * @ticket 52920 + */ + function test_get_default_block_editor_settings() { + $settings = gutenberg_get_default_block_editor_settings(); + + $this->assertCount( 16, $settings ); + $this->assertFalse( $settings['alignWide'] ); + $this->assertInternalType( 'array', $settings['allowedMimeTypes'] ); + $this->assertTrue( $settings['allowedBlockTypes'] ); + $this->assertSameSets( + array( + array( + 'slug' => 'text', + 'title' => 'Text', + 'icon' => null, + ), + array( + 'slug' => 'media', + 'title' => 'Media', + 'icon' => null, + ), + array( + 'slug' => 'design', + 'title' => 'Design', + 'icon' => null, + ), + array( + 'slug' => 'widgets', + 'title' => 'Widgets', + 'icon' => null, + ), + array( + 'slug' => 'theme', + 'title' => 'Theme', + 'icon' => null, + ), + array( + 'slug' => 'embed', + 'title' => 'Embeds', + 'icon' => null, + ), + array( + 'slug' => 'reusable', + 'title' => 'Reusable Blocks', + 'icon' => null, + ), + ), + $settings['blockCategories'] + ); + $this->assertFalse( $settings['disableCustomColors'] ); + $this->assertFalse( $settings['disableCustomFontSizes'] ); + $this->assertFalse( $settings['disableCustomGradients'] ); + $this->assertFalse( $settings['enableCustomLineHeight'] ); + $this->assertFalse( $settings['enableCustomSpacing'] ); + $this->assertFalse( $settings['enableCustomUnits'] ); + $this->assertFalse( $settings['isRTL'] ); + $this->assertSame( 'large', $settings['imageDefaultSize'] ); + $this->assertSameSets( + array( + array( + 'width' => 150, + 'height' => 150, + 'crop' => true, + ), + array( + 'width' => 300, + 'height' => 300, + 'crop' => false, + ), + array( + 'width' => 1024, + 'height' => 1024, + 'crop' => false, + ), + ), + $settings['imageDimensions'] + ); + $this->assertTrue( $settings['imageEditing'] ); + $this->assertSameSets( + array( + array( + 'slug' => 'full', + 'name' => 'Full Size', + ), + array( + 'slug' => 'large', + 'name' => 'Large', + ), + array( + 'slug' => 'medium', + 'name' => 'Medium', + ), + array( + 'slug' => 'thumbnail', + 'name' => 'Thumbnail', + ), + ), + $settings['imageSizes'] + ); + $this->assertInternalType( 'int', $settings['maxUploadFileSize'] ); + } + + /** + * @ticket 52920 + */ + function test_get_block_editor_settings_returns_default_settings() { + $this->assertSameSets( + gutenberg_get_block_editor_settings( 'post-editor' ), + gutenberg_get_default_block_editor_settings() + ); + } + + /** + * @ticket 52920 + */ + function test_get_block_editor_settings_overrides_default_settings_my_editor() { + function filter_allowed_block_types_my_editor() { + return array( 'test/filtered-my-block' ); + } + function filter_block_categories_my_editor() { + return array( + array( + 'slug' => 'filtered-my-category', + 'title' => 'Filtered My Category', + 'icon' => null, + ), + ); + } + function filter_block_editor_settings_my_editor( $editor_settings ) { + $editor_settings['maxUploadFileSize'] = 12345; + + return $editor_settings; + } + + add_filter( 'allowed_block_types_my-editor', 'filter_allowed_block_types_my_editor', 10, 1 ); + add_filter( 'block_categories_my-editor', 'filter_block_categories_my_editor', 10, 1 ); + add_filter( 'block_editor_settings_my-editor', 'filter_block_editor_settings_my_editor', 10, 1 ); + + $settings = get_block_editor_settings( 'my-editor' ); + + remove_filter( 'allowed_block_types_my-editor', 'filter_allowed_block_types_my_editor' ); + remove_filter( 'block_categories_my-editor', 'filter_block_categories_my_editor' ); + remove_filter( 'block_editor_settings_my-editor', 'filter_block_editor_settings_my_editor' ); + + $this->assertSameSets( array( 'test/filtered-my-block' ), $settings['allowedBlockTypes'] ); + $this->assertSameSets( + array( + array( + 'slug' => 'filtered-my-category', + 'title' => 'Filtered My Category', + 'icon' => null, + ), + ), + $settings['blockCategories'] + ); + $this->assertSame( 12345, $settings['maxUploadFileSize'] ); + } + + /** + * @ticket 52920 + * @expectedDeprecated block_editor_settings + */ + function test_get_block_editor_settings_deprecated_filter_post_editor() { + add_filter( 'block_editor_settings', array( $this, 'filter_set_block_editor_settings_post' ), 10, 2 ); + + $settings = get_block_editor_settings( 'post-editor' ); + + remove_filter( 'block_editor_settings', array( $this, 'filter_set_block_editor_settings_post' ) ); + + $this->assertSameSets( + array( + 'filter' => 'deprecated', + ), + $settings + ); + } +} diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 45470ac5979f0..840598364a0ee 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -959,7 +959,7 @@ function test_get_editor_settings_blank() { function test_get_editor_settings_custom_units_can_be_disabled() { add_theme_support( 'custom-units', array() ); - $input = gutenberg_get_common_block_editor_settings(); + $input = gutenberg_get_default_block_editor_settings(); $expected = array( 'units' => array( array() ), @@ -973,7 +973,7 @@ function test_get_editor_settings_custom_units_can_be_disabled() { function test_get_editor_settings_custom_units_can_be_enabled() { add_theme_support( 'custom-units' ); - $input = gutenberg_get_common_block_editor_settings(); + $input = gutenberg_get_default_block_editor_settings(); $expected = array( 'units' => array( 'px', 'em', 'rem', 'vh', 'vw' ), @@ -987,7 +987,7 @@ function test_get_editor_settings_custom_units_can_be_enabled() { function test_get_editor_settings_custom_units_can_be_filtered() { add_theme_support( 'custom-units', 'rem', 'em' ); - $input = gutenberg_get_common_block_editor_settings(); + $input = gutenberg_get_default_block_editor_settings(); $expected = array( 'units' => array( 'rem', 'em' ), From 73f94ca9d4355dec051b49a3cd9a60e2d8bcc7ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= Date: Mon, 19 Apr 2021 11:27:11 +0200 Subject: [PATCH 03/20] Block Editor: Bring back imageDefaultSize shim for WP 5.7 (#30955) --- lib/editor-settings.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/editor-settings.php b/lib/editor-settings.php index 0c8ed73d29c38..9a3b91c69a9f7 100644 --- a/lib/editor-settings.php +++ b/lib/editor-settings.php @@ -8,11 +8,20 @@ /** * Extends the block editor with settings that are only in the plugin. * + * This is a temporary solution until the Gutenberg plugin sets + * the required WordPress version to 5.8. + * + * @see https://core.trac.wordpress.org/ticket/52920 + * * @param array $settings Existing editor settings. * * @return array Filtered settings. */ function gutenberg_extend_post_editor_settings( $settings ) { + $image_default_size = get_option( 'image_default_size', 'large' ); + $image_sizes = wp_list_pluck( $settings['imageSizes'], 'slug' ); + + $settings['imageDefaultSize'] = in_array( $image_default_size, $image_sizes, true ) ? $image_default_size : 'large'; $settings['__unstableEnableFullSiteEditingBlocks'] = gutenberg_supports_block_templates(); return $settings; From 7d4b8a77b77e212d771d36dcd28460dad4db8b0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Mon, 19 Apr 2021 13:35:12 +0300 Subject: [PATCH 04/20] Block editor: scroll selected block only if it has no focus (#30924) --- packages/block-editor/README.md | 2 + .../src/components/block-list/index.js | 2 - .../block-list/use-block-props/index.js | 3 + .../use-block-props/use-scroll-into-view.js | 64 ++++++++++++++++++ .../selection-scroll-into-view/index.js | 67 ++----------------- 5 files changed, 76 insertions(+), 62 deletions(-) create mode 100644 packages/block-editor/src/components/block-list/use-block-props/use-scroll-into-view.js diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 15748d5e260c1..814eb12c18b76 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -448,6 +448,8 @@ _Related_ # **MultiSelectScrollIntoView** +> **Deprecated** + Scrolls the multi block selection end into view if not in view already. This is important to do after selection by keyboard. diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 8bd0ffe23252b..3eb229a068179 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -19,7 +19,6 @@ import useBlockDropZone from '../use-block-drop-zone'; import useInsertionPoint from './insertion-point'; import BlockPopover from './block-popover'; import { store as blockEditorStore } from '../../store'; -import { useScrollSelectionIntoView } from '../selection-scroll-into-view'; import { usePreParsePatterns } from '../../utils/pre-parse-patterns'; import { LayoutProvider, defaultLayout } from './layout'; @@ -30,7 +29,6 @@ export default function BlockList( { className, __experimentalLayout } ) { const ref = useRef(); const [ blockNodes, setBlockNodes ] = useState( {} ); const insertionPoint = useInsertionPoint( ref ); - useScrollSelectionIntoView( ref ); usePreParsePatterns(); const isLargeViewport = useViewportMatch( 'medium' ); diff --git a/packages/block-editor/src/components/block-list/use-block-props/index.js b/packages/block-editor/src/components/block-list/use-block-props/index.js index 44c70c213780d..91c27c025b4a6 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/index.js +++ b/packages/block-editor/src/components/block-list/use-block-props/index.js @@ -29,6 +29,7 @@ import { useBlockMovingModeClassNames } from './use-block-moving-mode-class-name import { useEventHandlers } from './use-event-handlers'; import { useNavModeExit } from './use-nav-mode-exit'; import { useBlockNodes } from './use-block-nodes'; +import { useScrollIntoView } from './use-scroll-into-view'; import { store as blockEditorStore } from '../../../store'; /** @@ -102,6 +103,8 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { const mergedRefs = useMergeRefs( [ props.ref, useFocusFirstElement( clientId ), + // Must happen after focus because we check for focus in the block. + useScrollIntoView( clientId ), useBlockNodes( clientId ), useEventHandlers( clientId ), useNavModeExit( clientId ), diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-scroll-into-view.js b/packages/block-editor/src/components/block-list/use-block-props/use-scroll-into-view.js new file mode 100644 index 0000000000000..3af1dfea02496 --- /dev/null +++ b/packages/block-editor/src/components/block-list/use-block-props/use-scroll-into-view.js @@ -0,0 +1,64 @@ +/** + * External dependencies + */ +import scrollIntoView from 'dom-scroll-into-view'; + +/** + * WordPress dependencies + */ +/** + * WordPress dependencies + */ +import { useEffect, useRef } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; +import { getScrollContainer } from '@wordpress/dom'; + +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../../../store'; + +export function useScrollIntoView( clientId ) { + const ref = useRef(); + const isSelectionEnd = useSelect( + ( select ) => { + const { isBlockSelected, getBlockSelectionEnd } = select( + blockEditorStore + ); + + return ( + isBlockSelected( clientId ) || + getBlockSelectionEnd() === clientId + ); + }, + [ clientId ] + ); + + useEffect( () => { + if ( ! isSelectionEnd ) { + return; + } + + const extentNode = ref.current; + + // If the block is focused, the browser will already have scrolled into + // view if necessary. + if ( extentNode.contains( extentNode.ownerDocument.activeElement ) ) { + return; + } + + const scrollContainer = getScrollContainer( extentNode ); + + // If there's no scroll container, it follows that there's no scrollbar + // and thus there's no need to try to scroll into view. + if ( ! scrollContainer ) { + return; + } + + scrollIntoView( extentNode, scrollContainer, { + onlyScrollIfNeeded: true, + } ); + }, [ isSelectionEnd ] ); + + return ref; +} diff --git a/packages/block-editor/src/components/selection-scroll-into-view/index.js b/packages/block-editor/src/components/selection-scroll-into-view/index.js index bbfb6f7370119..06922feae1499 100644 --- a/packages/block-editor/src/components/selection-scroll-into-view/index.js +++ b/packages/block-editor/src/components/selection-scroll-into-view/index.js @@ -1,70 +1,17 @@ -/** - * External dependencies - */ -import scrollIntoView from 'dom-scroll-into-view'; - /** * WordPress dependencies */ -import { useEffect, useRef } from '@wordpress/element'; -import { useSelect } from '@wordpress/data'; -import { getScrollContainer } from '@wordpress/dom'; - -/** - * Internal dependencies - */ -import { getBlockDOMNode } from '../../utils/dom'; -import { store as blockEditorStore } from '../../store'; - -export function useScrollSelectionIntoView( ref ) { - // Although selectionRootClientId isn't used directly in calculating - // whether scrolling should occur, it is used as a dependency of the - // effect to take into account situations where a block might be moved - // to a different parent. In this situation, the selectionEnd clientId - // remains the same, so the rootClientId is used to trigger the effect. - const { selectionRootClientId, selectionEnd } = useSelect( ( select ) => { - const selectors = select( blockEditorStore ); - const selectionEndClientId = selectors.getBlockSelectionEnd(); - return { - selectionEnd: selectionEndClientId, - selectionRootClientId: selectors.getBlockRootClientId( - selectionEndClientId - ), - }; - }, [] ); - - useEffect( () => { - if ( ! selectionEnd ) { - return; - } - - const { ownerDocument } = ref.current; - const extentNode = getBlockDOMNode( selectionEnd, ownerDocument ); - - if ( ! extentNode ) { - return; - } - - const scrollContainer = getScrollContainer( extentNode ); - - // If there's no scroll container, it follows that there's no scrollbar - // and thus there's no need to try to scroll into view. - if ( ! scrollContainer ) { - return; - } - - scrollIntoView( extentNode, scrollContainer, { - onlyScrollIfNeeded: true, - } ); - }, [ selectionRootClientId, selectionEnd ] ); -} +import deprecated from '@wordpress/deprecated'; /** * Scrolls the multi block selection end into view if not in view already. This * is important to do after selection by keyboard. + * + * @deprecated */ export function MultiSelectScrollIntoView() { - const ref = useRef(); - useScrollSelectionIntoView( ref ); - return
; + deprecated( 'wp.blockEditor.MultiSelectScrollIntoView', { + hint: 'This behaviour is now built-in.', + } ); + return null; } From be7ae0630c4ad20a0f77b55bd2deaca19aefe0f2 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 19 Apr 2021 21:02:32 +1000 Subject: [PATCH 05/20] BoxControl: Allow configurable sides (#30606) --- .../src/box-control/all-input-control.js | 9 +-- packages/components/src/box-control/icon.js | 24 ++++--- packages/components/src/box-control/index.js | 28 ++++---- .../src/box-control/input-controls.js | 67 ++++++++----------- .../box-control/styles/box-control-styles.js | 1 + .../src/input-control/input-field.js | 5 +- 6 files changed, 69 insertions(+), 65 deletions(-) diff --git a/packages/components/src/box-control/all-input-control.js b/packages/components/src/box-control/all-input-control.js index 1f1260898505d..ec6822f283fa5 100644 --- a/packages/components/src/box-control/all-input-control.js +++ b/packages/components/src/box-control/all-input-control.js @@ -14,6 +14,7 @@ export default function AllInputControl( { onHoverOn = noop, onHoverOff = noop, values, + sides, ...props } ) { const allValue = getAllValue( values ); @@ -28,11 +29,11 @@ export default function AllInputControl( { const handleOnChange = ( next ) => { const nextValues = { ...values }; + const selectedSides = sides?.length + ? sides + : [ 'top', 'right', 'bottom', 'left' ]; - nextValues.top = next; - nextValues.bottom = next; - nextValues.left = next; - nextValues.right = next; + selectedSides.forEach( ( side ) => ( nextValues[ side ] = next ) ); onChange( nextValues ); }; diff --git a/packages/components/src/box-control/icon.js b/packages/components/src/box-control/icon.js index 15249c01e4db1..f60375f627eb5 100644 --- a/packages/components/src/box-control/icon.js +++ b/packages/components/src/box-control/icon.js @@ -15,12 +15,24 @@ const BASE_ICON_SIZE = 24; export default function BoxControlIcon( { size = 24, side = 'all', + sides, ...props } ) { - const top = getSide( side, 'top' ); - const right = getSide( side, 'right' ); - const bottom = getSide( side, 'bottom' ); - const left = getSide( side, 'left' ); + const isSideDisabled = ( value ) => + sides?.length && ! sides.includes( value ); + + const getSide = ( value ) => { + if ( isSideDisabled( value ) ) { + return false; + } + + return side === 'all' || side === value; + }; + + const top = getSide( 'top' ); + const right = getSide( 'right' ); + const bottom = getSide( 'bottom' ); + const left = getSide( 'left' ); // Simulates SVG Icon scaling const scale = size / BASE_ICON_SIZE; @@ -36,7 +48,3 @@ export default function BoxControlIcon( { ); } - -function getSide( side, value ) { - return side === 'all' || side === value; -} diff --git a/packages/components/src/box-control/index.js b/packages/components/src/box-control/index.js index 187e8f1021401..9421792b56471 100644 --- a/packages/components/src/box-control/index.js +++ b/packages/components/src/box-control/index.js @@ -51,16 +51,19 @@ export default function BoxControl( { label = __( 'Box Control' ), values: valuesProp, units, + sides, + resetValues = DEFAULT_VALUES, } ) { const [ values, setValues ] = useControlledState( valuesProp, { fallback: DEFAULT_VALUES, } ); const inputValues = values || DEFAULT_VALUES; const hasInitialValue = isValuesDefined( valuesProp ); + const hasOneSide = sides?.length === 1; const [ isDirty, setIsDirty ] = useState( hasInitialValue ); const [ isLinked, setIsLinked ] = useState( - ! hasInitialValue || ! isValuesMixed( inputValues ) + ! hasInitialValue || ! isValuesMixed( inputValues ) || hasOneSide ); const [ side, setSide ] = useState( isLinked ? 'all' : 'top' ); @@ -92,10 +95,8 @@ export default function BoxControl( { }; const handleOnReset = () => { - const initialValues = DEFAULT_VALUES; - - onChange( initialValues ); - setValues( initialValues ); + onChange( resetValues ); + setValues( resetValues ); setIsDirty( false ); }; @@ -107,6 +108,7 @@ export default function BoxControl( { onHoverOff: handleOnHoverOff, isLinked, units, + sides, values: inputValues, }; @@ -135,7 +137,7 @@ export default function BoxControl( { - + { isLinked && ( @@ -145,12 +147,14 @@ export default function BoxControl( { /> ) } - - - + { ! hasOneSide && ( + + + + ) } { ! isLinked && } diff --git a/packages/components/src/box-control/input-controls.js b/packages/components/src/box-control/input-controls.js index 0177052151273..036f7054a5669 100644 --- a/packages/components/src/box-control/input-controls.js +++ b/packages/components/src/box-control/input-controls.js @@ -10,12 +10,15 @@ import UnitControl from './unit-control'; import { LABELS } from './utils'; import { LayoutContainer, Layout } from './styles/box-control-styles'; +const allSides = [ 'top', 'right', 'bottom', 'left' ]; + export default function BoxInputControls( { onChange = noop, onFocus = noop, onHoverOn = noop, onHoverOff = noop, values, + sides, ...props } ) { const createHandleOnFocus = ( side ) => ( event ) => { @@ -34,8 +37,6 @@ export default function BoxInputControls( { onChange( nextValues ); }; - const { top, right, bottom, left } = values; - const createHandleOnChange = ( side ) => ( next, { event } ) => { const { altKey } = event; const nextValues = { ...values }; @@ -66,6 +67,15 @@ export default function BoxInputControls( { handleOnChange( nextValues ); }; + // Filter sides if custom configuration provided, maintaining default order. + const filteredSides = sides?.length + ? allSides.filter( ( side ) => sides.includes( side ) ) + : allSides; + + const first = filteredSides[ 0 ]; + const last = filteredSides[ filteredSides.length - 1 ]; + const only = first === last && first; + return ( - - - - + { filteredSides.map( ( side ) => ( + + ) ) } ); 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..7d42aa42dcb18 100644 --- a/packages/components/src/box-control/styles/box-control-styles.js +++ b/packages/components/src/box-control/styles/box-control-styles.js @@ -40,6 +40,7 @@ export const Layout = styled( Flex )` position: relative; height: 100%; width: 100%; + justify-content: flex-start; `; const unitControlBorderRadiusStyles = ( { isFirst, isLast, isOnly } ) => { diff --git a/packages/components/src/input-control/input-field.js b/packages/components/src/input-control/input-field.js index 18044ee455ac9..5931c789f651b 100644 --- a/packages/components/src/input-control/input-field.js +++ b/packages/components/src/input-control/input-field.js @@ -71,7 +71,7 @@ function InputField( const dragCursor = useDragCursor( isDragging, dragDirection ); /* - * Handles syncronization of external and internal value state. + * Handles synchronization of external and internal value state. * If not focused and did not hold a dirty value[1] on blur * updates the value from the props. Otherwise if not holding * a dirty value[1] propagates the value and event through onChange. @@ -155,6 +155,9 @@ function InputField( const dragGestureProps = useDrag( ( dragProps ) => { const { distance, dragging, event } = dragProps; + // The event is persisted to prevent errors in components using this + // to check if a modifier key was held while dragging. + event.persist(); if ( ! distance ) return; event.stopPropagation(); From b11bc8d90a610cf21b0c8a92eef349d141d71de5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ella=20van=C2=A0Durpe?= Date: Mon, 19 Apr 2021 14:53:43 +0300 Subject: [PATCH 06/20] Drop zone: rewrite without provider (#30310) * Drop zone: rewrite without provider * Remove providers * Use ref callback * Simplify * Fix drag start * Fix navigator * Fix dead zone writing flow * Fix e2e test * Address feedback * Avoid disabling pointer events and treat throttling as an implementation detail * Use dragenter for detecting the start of dragging * Move useDropZone to compose --- .../src/components/block-draggable/index.js | 1 + .../src/components/block-list/index.js | 11 +- .../components/block-list/insertion-point.js | 4 +- .../src/components/block-list/style.scss | 13 + .../src/components/block-navigation/tree.js | 8 +- .../use-block-navigation-drop-zone.js | 194 ++++++------- .../src/components/inner-blocks/index.js | 19 +- .../components/use-block-drop-zone/index.js | 64 ++--- .../src/components/use-on-block-drop/index.js | 60 ++-- packages/components/src/draggable/index.js | 7 +- packages/components/src/drop-zone/README.md | 22 +- packages/components/src/drop-zone/index.js | 115 ++++---- packages/components/src/drop-zone/provider.js | 262 +----------------- packages/components/src/drop-zone/style.scss | 4 - packages/components/src/index.js | 11 +- .../compose/src/hooks/use-drop-zone/index.js | 181 ++++++++++++ packages/compose/src/index.js | 1 + .../components/sidebar-block-editor/index.js | 88 +++--- .../editor/various/draggable-block.test.js | 4 +- .../src/components/layout/index.js | 227 ++++++++------- .../edit-post/src/components/layout/index.js | 9 +- packages/edit-post/src/editor.js | 46 ++- .../src/components/block-editor/index.js | 24 +- .../edit-site/src/components/editor/index.js | 169 ++++++----- .../index.js | 28 +- storybook/stories/playground/index.js | 48 ++-- 26 files changed, 732 insertions(+), 888 deletions(-) create mode 100644 packages/compose/src/hooks/use-drop-zone/index.js diff --git a/packages/block-editor/src/components/block-draggable/index.js b/packages/block-editor/src/components/block-draggable/index.js index ccc86cdd1ab0c..71da2b075c49c 100644 --- a/packages/block-editor/src/components/block-draggable/index.js +++ b/packages/block-editor/src/components/block-draggable/index.js @@ -76,6 +76,7 @@ const BlockDraggable = ( { { startDraggingBlocks( clientIds ); diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 3eb229a068179..352df1fd8a4f3 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -8,7 +8,7 @@ import classnames from 'classnames'; */ import { AsyncModeProvider, useSelect } from '@wordpress/data'; import { useRef, createContext, useState } from '@wordpress/element'; -import { useViewportMatch } from '@wordpress/compose'; +import { useViewportMatch, useMergeRefs } from '@wordpress/compose'; /** * Internal dependencies @@ -57,7 +57,7 @@ export default function BlockList( { className, __experimentalLayout } ) { { insertionPoint }
@@ -86,7 +85,6 @@ function Items( { renderAppender, __experimentalAppenderTagName, __experimentalLayout: layout = defaultLayout, - wrapperRef, } ) { function selector( select ) { const { @@ -110,11 +108,6 @@ function Items( { hasMultiSelection, } = useSelect( selector, [ rootClientId ] ); - useBlockDropZone( { - element: wrapperRef, - rootClientId, - } ); - return ( { blockClientIds.map( ( clientId, index ) => { diff --git a/packages/block-editor/src/components/block-list/insertion-point.js b/packages/block-editor/src/components/block-list/insertion-point.js index d759d568efd3e..8f26bf95dfabb 100644 --- a/packages/block-editor/src/components/block-list/insertion-point.js +++ b/packages/block-editor/src/components/block-list/insertion-point.js @@ -233,7 +233,9 @@ function InsertionPointPopover( { tabIndex={ -1 } onClick={ onClick } onFocus={ onFocus } - className={ className } + className={ classnames( className, { + 'is-with-inserter': showInsertionPointInserter, + } ) } style={ style } > { showInsertionPointIndicator && ( diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index 8deaed9eb89ca..16422a98d9376 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -650,6 +650,19 @@ .block-editor-block-list__insertion-point-popover.is-without-arrow { z-index: z-index(".block-editor-block-list__insertion-point-popover"); position: absolute; + pointer-events: none; + + * { + pointer-events: none; + } + + .is-with-inserter { + pointer-events: all; + + * { + pointer-events: all; + } + } .components-popover__content.components-popover__content { // Needs specificity. background: none; diff --git a/packages/block-editor/src/components/block-navigation/tree.js b/packages/block-editor/src/components/block-navigation/tree.js index e159c30973903..c9ca266f7b1ac 100644 --- a/packages/block-editor/src/components/block-navigation/tree.js +++ b/packages/block-editor/src/components/block-navigation/tree.js @@ -3,7 +3,7 @@ */ import { __experimentalTreeGrid as TreeGrid } from '@wordpress/components'; -import { useMemo, useRef } from '@wordpress/element'; +import { useMemo } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -26,8 +26,10 @@ export default function BlockNavigationTree( { __experimentalPersistentListViewFeatures, ...props } ) { - const treeGridRef = useRef(); - let blockDropTarget = useBlockNavigationDropZone( treeGridRef ); + let { + ref: treeGridRef, + target: blockDropTarget, + } = useBlockNavigationDropZone(); if ( ! __experimentalFeatures ) { blockDropTarget = undefined; diff --git a/packages/block-editor/src/components/block-navigation/use-block-navigation-drop-zone.js b/packages/block-editor/src/components/block-navigation/use-block-navigation-drop-zone.js index f7a6853fbd6b9..cc24aa5b02a5a 100644 --- a/packages/block-editor/src/components/block-navigation/use-block-navigation-drop-zone.js +++ b/packages/block-editor/src/components/block-navigation/use-block-navigation-drop-zone.js @@ -1,9 +1,12 @@ /** * WordPress dependencies */ -import { __unstableUseDropZone as useDropZone } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; -import { useEffect, useRef, useState } from '@wordpress/element'; +import { useState, useCallback } from '@wordpress/element'; +import { + useThrottle, + __experimentalUseDropZone as useDropZone, +} from '@wordpress/compose'; /** * Internal dependencies @@ -13,7 +16,6 @@ import useOnBlockDrop from '../use-on-block-drop'; import { store as blockEditorStore } from '../../store'; /** @typedef {import('../../utils/math').WPPoint} WPPoint */ -/** @typedef {import('@wordpress/element').RefObject} RefObject */ /** * The type of a drag event. @@ -46,91 +48,6 @@ import { store as blockEditorStore } from '../../store'; * 'inside' refers to nesting as an inner block. */ -/** - * A react hook that returns data about blocks used for computing where a user - * can drop to when dragging and dropping blocks. - * - * @param {Object} ref A React ref of a containing element for block navigation. - * @param {WPPoint} position The current drag position. - * @param {WPDragEventType} dragEventType The drag event type. - * - * @return {RefObject} A React ref containing the blocks data. - */ -function useDropTargetBlocksData( ref, position, dragEventType ) { - const { - getBlockRootClientId, - getBlockIndex, - getBlockCount, - getDraggedBlockClientIds, - canInsertBlocks, - } = useSelect( ( select ) => { - const selectors = select( blockEditorStore ); - return { - canInsertBlocks: selectors.canInsertBlocks, - getBlockRootClientId: selectors.getBlockRootClientId, - getBlockIndex: selectors.getBlockIndex, - getBlockCount: selectors.getBlockCount, - getDraggedBlockClientIds: selectors.getDraggedBlockClientIds, - }; - }, [] ); - const blocksData = useRef(); - - // Compute data about blocks only when the user - // starts dragging, as determined by `hasPosition`. - const hasPosition = !! position; - - useEffect( () => { - if ( ! ref.current || ! hasPosition ) { - return; - } - - const isBlockDrag = dragEventType === 'default'; - - const draggedBlockClientIds = isBlockDrag - ? getDraggedBlockClientIds() - : undefined; - - const blockElements = Array.from( - ref.current.querySelectorAll( '[data-block]' ) - ); - - blocksData.current = blockElements.map( ( blockElement ) => { - const clientId = blockElement.dataset.block; - const rootClientId = getBlockRootClientId( clientId ); - - return { - clientId, - rootClientId, - blockIndex: getBlockIndex( clientId, rootClientId ), - element: blockElement, - isDraggedBlock: isBlockDrag - ? draggedBlockClientIds.includes( clientId ) - : false, - innerBlockCount: getBlockCount( clientId ), - canInsertDraggedBlocksAsSibling: isBlockDrag - ? canInsertBlocks( draggedBlockClientIds, rootClientId ) - : true, - canInsertDraggedBlocksAsChild: isBlockDrag - ? canInsertBlocks( draggedBlockClientIds, clientId ) - : true, - }; - } ); - }, [ - // `ref` shouldn't actually change during a drag operation, but - // is specified for completeness as it's used within the hook. - ref, - hasPosition, - dragEventType, - canInsertBlocks, - getBlockCount, - getBlockIndex, - getBlockRootClientId, - getDraggedBlockClientIds, - ] ); - - return blocksData; -} - /** * Is the point contained by the rectangle. * @@ -275,45 +192,90 @@ function getBlockNavigationDropTarget( blocksData, position ) { /** * A react hook for implementing a drop zone in block navigation. * - * @param {Object} ref A React ref of a containing element for block navigation. - * * @return {WPBlockNavigationDropZoneTarget} The drop target. */ -export default function useBlockNavigationDropZone( ref ) { - const [ target = {}, setTarget ] = useState(); +export default function useBlockNavigationDropZone() { const { - rootClientId: targetRootClientId, - blockIndex: targetBlockIndex, - } = target; - - const dropEventHandlers = useOnBlockDrop( - targetRootClientId, - targetBlockIndex - ); - - const { position, type: dragEventType } = useDropZone( { - element: ref, - withPosition: true, - ...dropEventHandlers, - } ); + getBlockRootClientId, + getBlockIndex, + getBlockCount, + getDraggedBlockClientIds, + canInsertBlocks, + } = useSelect( ( select ) => { + const selectors = select( blockEditorStore ); + return { + canInsertBlocks: selectors.canInsertBlocks, + getBlockRootClientId: selectors.getBlockRootClientId, + getBlockIndex: selectors.getBlockIndex, + getBlockCount: selectors.getBlockCount, + getDraggedBlockClientIds: selectors.getDraggedBlockClientIds, + }; + }, [] ); + const [ target, setTarget ] = useState(); + const { rootClientId: targetRootClientId, blockIndex: targetBlockIndex } = + target || {}; + + const onBlockDrop = useOnBlockDrop( targetRootClientId, targetBlockIndex ); + const throttled = useThrottle( + useCallback( ( event, currentTarget ) => { + const position = { x: event.clientX, y: event.clientY }; + const isBlockDrag = !! event.dataTransfer.getData( 'wp-blocks' ); + + const draggedBlockClientIds = isBlockDrag + ? getDraggedBlockClientIds() + : undefined; + + const blockElements = Array.from( + currentTarget.querySelectorAll( '[data-block]' ) + ); - const blocksData = useDropTargetBlocksData( ref, position, dragEventType ); + const blocksData = blockElements.map( ( blockElement ) => { + const clientId = blockElement.dataset.block; + const rootClientId = getBlockRootClientId( clientId ); + + return { + clientId, + rootClientId, + blockIndex: getBlockIndex( clientId, rootClientId ), + element: blockElement, + isDraggedBlock: isBlockDrag + ? draggedBlockClientIds.includes( clientId ) + : false, + innerBlockCount: getBlockCount( clientId ), + canInsertDraggedBlocksAsSibling: isBlockDrag + ? canInsertBlocks( draggedBlockClientIds, rootClientId ) + : true, + canInsertDraggedBlocksAsChild: isBlockDrag + ? canInsertBlocks( draggedBlockClientIds, clientId ) + : true, + }; + } ); - // Calculate the drop target based on the drag position. - useEffect( () => { - if ( position ) { const newTarget = getBlockNavigationDropTarget( - blocksData.current, + blocksData, position ); if ( newTarget ) { setTarget( newTarget ); } - } - }, [ blocksData, position ] ); + }, [] ), + 200 + ); - if ( position ) { - return target; - } + const ref = useDropZone( { + onDrop: onBlockDrop, + onDragOver( event ) { + // `currentTarget` is only available while the event is being + // handled, so get it now and pass it to the thottled function. + // https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget + throttled( event, event.currentTarget ); + }, + onDragEnd() { + throttled.cancel(); + setTarget( null ); + }, + } ); + + return { ref, target }; } diff --git a/packages/block-editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js index e826f3f9a1c52..0744fdce4e5da 100644 --- a/packages/block-editor/src/components/inner-blocks/index.js +++ b/packages/block-editor/src/components/inner-blocks/index.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { useViewportMatch, useMergeRefs } from '@wordpress/compose'; -import { forwardRef, useRef } from '@wordpress/element'; +import { forwardRef } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; import { getBlockType, withBlockContentContext } from '@wordpress/blocks'; @@ -24,6 +24,7 @@ import { BlockContextProvider } from '../block-context'; import { useBlockEditContext } from '../block-edit/context'; import useBlockSync from '../provider/use-block-sync'; import { store as blockEditorStore } from '../../store'; +import useBlockDropZone from '../use-block-drop-zone'; /** * InnerBlocks is a component which allows a single block to have multiple blocks @@ -133,7 +134,6 @@ const ForwardedInnerBlocks = forwardRef( ( props, ref ) => { * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/inner-blocks/README.md */ export function useInnerBlocksProps( props = {}, options = {} ) { - const fallbackRef = useRef(); const { clientId } = useBlockEditContext(); const isSmallScreen = useViewportMatch( 'medium', '<' ); const hasOverlay = useSelect( @@ -155,7 +155,12 @@ export function useInnerBlocksProps( props = {}, options = {} ) { [ clientId, isSmallScreen ] ); - const ref = useMergeRefs( [ props.ref, fallbackRef ] ); + const ref = useMergeRefs( [ + props.ref, + useBlockDropZone( { + rootClientId: clientId, + } ), + ] ); const InnerBlocks = options.value && options.onChange ? ControlledInnerBlocks @@ -171,13 +176,7 @@ export function useInnerBlocksProps( props = {}, options = {} ) { 'has-overlay': hasOverlay, } ), - children: ( - - ), + children: , }; } diff --git a/packages/block-editor/src/components/use-block-drop-zone/index.js b/packages/block-editor/src/components/use-block-drop-zone/index.js index b19c6a861563a..e27973395b9b6 100644 --- a/packages/block-editor/src/components/use-block-drop-zone/index.js +++ b/packages/block-editor/src/components/use-block-drop-zone/index.js @@ -1,9 +1,12 @@ /** * WordPress dependencies */ -import { __unstableUseDropZone as useDropZone } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; -import { useEffect, useState } from '@wordpress/element'; +import { useCallback, useState } from '@wordpress/element'; +import { + useThrottle, + __experimentalUseDropZone as useDropZone, +} from '@wordpress/compose'; /** * Internal dependencies @@ -78,7 +81,6 @@ export function getNearestBlockIndex( elements, position, orientation ) { /** * @typedef {Object} WPBlockDropZoneConfig - * @property {Object} element A React ref object pointing to the block list's DOM element. * @property {string} rootClientId The root client id for the block list. */ @@ -88,13 +90,12 @@ export function getNearestBlockIndex( elements, position, orientation ) { * @param {WPBlockDropZoneConfig} dropZoneConfig configuration data for the drop zone. */ export default function useBlockDropZone( { - element, // An undefined value represents a top-level block. Default to an empty // string for this so that `targetRootClientId` can be easily compared to // values returned by the `getRootBlockClientId` selector, which also uses // an empty string to represent top-level blocks. rootClientId: targetRootClientId = '', -} ) { +} = {} ) { const [ targetBlockIndex, setTargetBlockIndex ] = useState( null ); const { isLockedAll, orientation } = useSelect( @@ -115,39 +116,38 @@ export default function useBlockDropZone( { 'core/block-editor' ); - const dropEventHandlers = useOnBlockDrop( - targetRootClientId, - targetBlockIndex - ); - - const { position, isDraggingOverDocument } = useDropZone( { - element, - isDisabled: isLockedAll, - withPosition: true, - ...dropEventHandlers, - } ); - - useEffect( () => { - if ( position ) { - const blockElements = Array.from( element.current.children ); - + const onBlockDrop = useOnBlockDrop( targetRootClientId, targetBlockIndex ); + const throttled = useThrottle( + useCallback( ( event, currentTarget ) => { + const blockElements = Array.from( currentTarget.children ); const targetIndex = getNearestBlockIndex( blockElements, - position, + { x: event.clientX, y: event.clientY }, orientation ); setTargetBlockIndex( targetIndex === undefined ? 0 : targetIndex ); - } else { - setTargetBlockIndex( null ); - } - }, [ position ] ); - useEffect( () => { - if ( ! isDraggingOverDocument ) { + if ( targetIndex !== null ) { + showInsertionPoint( targetRootClientId, targetIndex ); + } + }, [] ), + 200 + ); + + return useDropZone( { + isDisabled: isLockedAll, + onDrop: onBlockDrop, + onDragOver( event ) { + // `currentTarget` is only available while the event is being + // handled, so get it now and pass it to the thottled function. + // https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget + throttled( event, event.currentTarget ); + }, + onDragEnd() { + throttled.cancel(); hideInsertionPoint(); - } else if ( targetBlockIndex !== null ) { - showInsertionPoint( targetRootClientId, targetBlockIndex ); - } - }, [ targetBlockIndex, isDraggingOverDocument ] ); + setTargetBlockIndex( null ); + }, + } ); } diff --git a/packages/block-editor/src/components/use-on-block-drop/index.js b/packages/block-editor/src/components/use-on-block-drop/index.js index f2ee0ee1c0182..cfc36bf10f130 100644 --- a/packages/block-editor/src/components/use-on-block-drop/index.js +++ b/packages/block-editor/src/components/use-on-block-drop/index.js @@ -7,6 +7,7 @@ import { pasteHandler, } from '@wordpress/blocks'; import { useDispatch, useSelect } from '@wordpress/data'; +import { getFilesFromDataTransfer } from '@wordpress/dom'; /** * Internal dependencies @@ -38,7 +39,7 @@ export function parseDropEvent( event ) { try { result = Object.assign( result, - JSON.parse( event.dataTransfer.getData( 'text' ) ) + JSON.parse( event.dataTransfer.getData( 'wp-blocks' ) ) ); } catch ( err ) { return result; @@ -239,28 +240,39 @@ export default function useOnBlockDrop( targetRootClientId, targetBlockIndex ) { clearSelectedBlock, } = useDispatch( blockEditorStore ); - return { - onDrop: onBlockDrop( - targetRootClientId, - targetBlockIndex, - getBlockIndex, - getClientIdsOfDescendants, - moveBlocksToPosition, - insertBlocks, - clearSelectedBlock - ), - onFilesDrop: onFilesDrop( - targetRootClientId, - targetBlockIndex, - hasUploadPermissions, - updateBlockAttributes, - canInsertBlockType, - insertBlocks - ), - onHTMLDrop: onHTMLDrop( - targetRootClientId, - targetBlockIndex, - insertBlocks - ), + const _onDrop = onBlockDrop( + targetRootClientId, + targetBlockIndex, + getBlockIndex, + getClientIdsOfDescendants, + moveBlocksToPosition, + insertBlocks, + clearSelectedBlock + ); + const _onFilesDrop = onFilesDrop( + targetRootClientId, + targetBlockIndex, + hasUploadPermissions, + updateBlockAttributes, + canInsertBlockType, + insertBlocks + ); + const _onHTMLDrop = onHTMLDrop( + targetRootClientId, + targetBlockIndex, + insertBlocks + ); + + return ( event ) => { + const files = getFilesFromDataTransfer( event.dataTransfer ); + const html = event.dataTransfer.getData( 'text/html' ); + + if ( files.length ) { + _onFilesDrop( files ); + } else if ( html ) { + _onHTMLDrop( html ); + } else { + _onDrop( event ); + } }; } diff --git a/packages/components/src/draggable/index.js b/packages/components/src/draggable/index.js index bb8b8fe68eed0..e4a9b43059da8 100644 --- a/packages/components/src/draggable/index.js +++ b/packages/components/src/draggable/index.js @@ -24,6 +24,7 @@ const bodyClass = 'is-dragging-components-draggable'; * @property {string} [cloneClassname] Classname for the cloned element. * @property {string} [elementId] ID for the element. * @property {any} [transferData] Transfer data for the drag event. + * @property {string} [__experimentalTransferDataType] The transfer data type to set. * @property {import('react').ReactNode} __experimentalDragComponent Component to show when dragging. */ @@ -39,6 +40,7 @@ export default function Draggable( { cloneClassname, elementId, transferData, + __experimentalTransferDataType: transferDataType = 'text', __experimentalDragComponent: dragComponent, } ) { /** @type {import('react').MutableRefObject} */ @@ -73,7 +75,10 @@ export default function Draggable( { // @ts-ignore We know that ownerDocument does exist on an Element const { ownerDocument } = event.target; - event.dataTransfer.setData( 'text', JSON.stringify( transferData ) ); + event.dataTransfer.setData( + transferDataType, + JSON.stringify( transferData ) + ); const cloneWrapper = ownerDocument.createElement( 'div' ); const dragImage = ownerDocument.createElement( 'div' ); diff --git a/packages/components/src/drop-zone/README.md b/packages/components/src/drop-zone/README.md index 2a1b8f2532fc7..09fe5fbd5fedc 100644 --- a/packages/components/src/drop-zone/README.md +++ b/packages/components/src/drop-zone/README.md @@ -1,26 +1,24 @@ # DropZone -`DropZone` is a Component creating a drop zone area taking the full size of its parent element. It supports dropping files, HTML content or any other HTML drop event. To work properly this components needs to be wrapped in a `DropZoneProvider`. +`DropZone` is a Component creating a drop zone area taking the full size of its parent element. It supports dropping files, HTML content or any other HTML drop event. ## Usage ```jsx -import { DropZoneProvider, DropZone } from '@wordpress/components'; +import { DropZone } from '@wordpress/components'; import { withState } from '@wordpress/compose'; const MyDropZone = withState( { hasDropped: false, } )( ( { hasDropped, setState } ) => ( - -
- { hasDropped ? 'Dropped!' : 'Drop something here' } - setState( { hasDropped: true } ) } - onHTMLDrop={ () => setState( { hasDropped: true } ) } - onDrop={ () => setState( { hasDropped: true } ) } - /> -
-
+
+ { hasDropped ? 'Dropped!' : 'Drop something here' } + setState( { hasDropped: true } ) } + onHTMLDrop={ () => setState( { hasDropped: true } ) } + onDrop={ () => setState( { hasDropped: true } ) } + /> +
) ); ``` diff --git a/packages/components/src/drop-zone/index.js b/packages/components/src/drop-zone/index.js index 338d19b4f98e4..293f0a64b183c 100644 --- a/packages/components/src/drop-zone/index.js +++ b/packages/components/src/drop-zone/index.js @@ -2,86 +2,69 @@ * External dependencies */ import classnames from 'classnames'; +import { includes } from 'lodash'; /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { useContext, useEffect, useState, useRef } from '@wordpress/element'; +import { useState } from '@wordpress/element'; import { upload, Icon } from '@wordpress/icons'; +import { getFilesFromDataTransfer } from '@wordpress/dom'; +import { __experimentalUseDropZone as useDropZone } from '@wordpress/compose'; -/** - * Internal dependencies - */ -import { Context, INITIAL_DROP_ZONE_STATE } from './provider'; - -export function useDropZone( { - element, +export default function DropZoneComponent( { + className, + label, onFilesDrop, onHTMLDrop, onDrop, - isDisabled, - withPosition, - __unstableIsRelative: isRelative = false, } ) { - const dropZones = useContext( Context ); - const [ state, setState ] = useState( INITIAL_DROP_ZONE_STATE ); - - useEffect( () => { - if ( ! isDisabled ) { - const dropZone = { - element, - onDrop, - onFilesDrop, - onHTMLDrop, - setState, - withPosition, - isRelative, - }; - dropZones.add( dropZone ); - return () => { - dropZones.delete( dropZone ); - }; - } - }, [ - isDisabled, - onDrop, - onFilesDrop, - onHTMLDrop, - withPosition, - isRelative, - ] ); + const [ isDraggingOverDocument, setIsDraggingOverDocument ] = useState(); + const [ isDraggingOverElement, setIsDraggingOverElement ] = useState(); + const [ type, setType ] = useState(); + const ref = useDropZone( { + onDrop( event ) { + const files = getFilesFromDataTransfer( event.dataTransfer ); + const html = event.dataTransfer.getData( 'text/html' ); - const { x, y, ...remainingState } = state; - let position = null; + if ( files.length && onFilesDrop ) { + onFilesDrop( files ); + } else if ( html && onHTMLDrop ) { + onHTMLDrop( html ); + } else if ( onDrop ) { + onDrop( event ); + } + }, + onDragStart( event ) { + setIsDraggingOverDocument( true ); - if ( x !== null && y !== null ) { - position = { x, y }; - } + let _type = 'default'; - return { - ...remainingState, - position, - }; -} + if ( + // Check for the types because sometimes the files themselves + // are only available on drop. + includes( event.dataTransfer.types, 'Files' ) || + getFilesFromDataTransfer( event.dataTransfer ).length > 0 + ) { + _type = 'file'; + } else if ( includes( event.dataTransfer.types, 'text/html' ) ) { + _type = 'html'; + } -export default function DropZoneComponent( { - className, - label, - onFilesDrop, - onHTMLDrop, - onDrop, -} ) { - const element = useRef(); - const { isDraggingOverDocument, isDraggingOverElement, type } = useDropZone( - { - element, - onFilesDrop, - onHTMLDrop, - onDrop, - __unstableIsRelative: true, - } - ); + setType( _type ); + }, + onDragEnd() { + setIsDraggingOverDocument( false ); + setType(); + }, + onDragEnter() { + setIsDraggingOverElement( true ); + }, + onDragLeave() { + setIsDraggingOverElement( false ); + }, + } ); let children; @@ -111,7 +94,7 @@ export default function DropZoneComponent( { } ); return ( -
+
{ children }
); diff --git a/packages/components/src/drop-zone/provider.js b/packages/components/src/drop-zone/provider.js index e2d06792cc3c6..3a4a506f04b11 100644 --- a/packages/components/src/drop-zone/provider.js +++ b/packages/components/src/drop-zone/provider.js @@ -1,262 +1,12 @@ -/** - * External dependencies - */ -import { find, some, filter, includes, throttle } from 'lodash'; - /** * WordPress dependencies */ -import { createContext, useRef, useContext } from '@wordpress/element'; -import { getFilesFromDataTransfer } from '@wordpress/dom'; -import isShallowEqual from '@wordpress/is-shallow-equal'; -import { useRefEffect } from '@wordpress/compose'; - -export const Context = createContext(); - -const { Provider } = Context; - -function getDragEventType( { dataTransfer } ) { - if ( dataTransfer ) { - // Use lodash `includes` here as in the Edge browser `types` is implemented - // as a DomStringList, whereas in other browsers it's an array. `includes` - // happily works with both types. - if ( - includes( dataTransfer.types, 'Files' ) || - getFilesFromDataTransfer( dataTransfer ).length > 0 - ) { - return 'file'; - } - - if ( includes( dataTransfer.types, 'text/html' ) ) { - return 'html'; - } - } - - return 'default'; -} - -function isTypeSupportedByDropZone( type, dropZone ) { - return Boolean( - ( type === 'file' && dropZone.onFilesDrop ) || - ( type === 'html' && dropZone.onHTMLDrop ) || - ( type === 'default' && dropZone.onDrop ) - ); -} - -function isWithinElementBounds( element, x, y ) { - const rect = element.getBoundingClientRect(); - /// make sure the rect is a valid rect - if ( rect.bottom === rect.top || rect.left === rect.right ) { - return false; - } - - return ( - x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom - ); -} - -function getPosition( event ) { - // In some contexts, it may be necessary to capture and redirect the - // drag event (e.g. atop an `iframe`). To accommodate this, you can - // create an instance of CustomEvent with the original event specified - // as the `detail` property. - // - // See: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events - const detail = - window.CustomEvent && event instanceof window.CustomEvent - ? event.detail - : event; - - return { x: detail.clientX, y: detail.clientY }; -} - -function getHoveredDropZone( dropZones, position, dragEventType ) { - const hoveredDropZones = filter( - Array.from( dropZones ), - ( dropZone ) => - isTypeSupportedByDropZone( dragEventType, dropZone ) && - isWithinElementBounds( - dropZone.element.current, - position.x, - position.y - ) - ); - - // Find the leaf dropzone not containing another dropzone - return find( hoveredDropZones, ( zone ) => { - const container = zone.isRelative - ? zone.element.current.parentElement - : zone.element.current; - - return ! some( - hoveredDropZones, - ( subZone ) => - subZone !== zone && - container.contains( subZone.element.current ) - ); - } ); -} - -export const INITIAL_DROP_ZONE_STATE = { - isDraggingOverDocument: false, - isDraggingOverElement: false, - x: null, - y: null, - type: null, -}; - -export function useDrop() { - const dropZones = useContext( Context ); - - return useRefEffect( - ( node ) => { - const { ownerDocument } = node; - const { defaultView } = ownerDocument; - - let lastRelative; - - function updateDragZones( event ) { - if ( lastRelative && lastRelative.contains( event.target ) ) { - return; - } - - const dragEventType = getDragEventType( event ); - const position = getPosition( event ); - const hoveredDropZone = getHoveredDropZone( - dropZones, - position, - dragEventType - ); - - if ( hoveredDropZone && hoveredDropZone.isRelative ) { - lastRelative = hoveredDropZone.element.current.offsetParent; - } else { - lastRelative = null; - } - - // Notifying the dropzones - dropZones.forEach( ( dropZone ) => { - const isDraggingOverDropZone = dropZone === hoveredDropZone; - const newState = { - isDraggingOverDocument: isTypeSupportedByDropZone( - dragEventType, - dropZone - ), - isDraggingOverElement: isDraggingOverDropZone, - x: - isDraggingOverDropZone && dropZone.withPosition - ? position.x - : null, - y: - isDraggingOverDropZone && dropZone.withPosition - ? position.y - : null, - type: isDraggingOverDropZone ? dragEventType : null, - }; - - dropZone.setState( ( state ) => { - if ( isShallowEqual( state, newState ) ) { - return state; - } - - return newState; - } ); - } ); - - event.preventDefault(); - } - - const throttledUpdateDragZones = throttle( updateDragZones, 200 ); - - function onDragOver( event ) { - throttledUpdateDragZones( event ); - event.preventDefault(); - } - - function resetDragState() { - // Avoid throttled drag over handler calls - throttledUpdateDragZones.cancel(); - - dropZones.forEach( ( dropZone ) => - dropZone.setState( INITIAL_DROP_ZONE_STATE ) - ); - } - - function onDrop( event ) { - // This seemingly useless line has been shown to resolve a Safari issue - // where files dragged directly from the dock are not recognized - event.dataTransfer && event.dataTransfer.files.length; // eslint-disable-line no-unused-expressions - - const dragEventType = getDragEventType( event ); - const position = getPosition( event ); - const hoveredDropZone = getHoveredDropZone( - dropZones, - position, - dragEventType - ); - - resetDragState(); - - if ( hoveredDropZone ) { - switch ( dragEventType ) { - case 'file': - hoveredDropZone.onFilesDrop( - getFilesFromDataTransfer( event.dataTransfer ), - position - ); - break; - case 'html': - hoveredDropZone.onHTMLDrop( - event.dataTransfer.getData( 'text/html' ), - position - ); - break; - case 'default': - hoveredDropZone.onDrop( event, position ); - } - } - - event.stopPropagation(); - event.preventDefault(); - } - - node.addEventListener( 'drop', onDrop ); - defaultView.addEventListener( 'dragover', onDragOver ); - defaultView.addEventListener( 'mouseup', resetDragState ); - // Note that `dragend` doesn't fire consistently for file and HTML drag - // events where the drag origin is outside the browser window. - // In Firefox it may also not fire if the originating node is removed. - defaultView.addEventListener( 'dragend', resetDragState ); - - return () => { - node.removeEventListener( 'drop', onDrop ); - defaultView.removeEventListener( 'dragover', onDragOver ); - defaultView.removeEventListener( 'mouseup', resetDragState ); - defaultView.removeEventListener( 'dragend', resetDragState ); - }; - }, - [ dropZones ] - ); -} - -export function DropZoneContextProvider( props ) { - const ref = useRef( new Set( [] ) ); - return ; -} - -function DropContainer( { children } ) { - const ref = useDrop(); - return ( -
- { children } -
- ); -} +import deprecated from '@wordpress/deprecated'; export default function DropZoneProvider( { children } ) { - return ( - - { children } - - ); + deprecated( 'wp.components.DropZoneProvider', { + hint: + 'wp.component.DropZone no longer needs a provider. wp.components.DropZoneProvider is safe to remove from your code.', + } ); + return children; } diff --git a/packages/components/src/drop-zone/style.scss b/packages/components/src/drop-zone/style.scss index e037e70a6f66f..012edd3ce8bed 100644 --- a/packages/components/src/drop-zone/style.scss +++ b/packages/components/src/drop-zone/style.scss @@ -57,7 +57,3 @@ .components-drop-zone__content-text { font-family: $default-font; } - -.components-drop-zone__provider { - height: 100%; -} diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 0c67e9330f035..d89ec5a389aed 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -48,15 +48,8 @@ export { default as __experimentalDimensionControl } from './dimension-control'; export { default as Disabled } from './disabled'; export { DisclosureContent as __unstableDisclosureContent } from './disclosure'; export { default as Draggable } from './draggable'; -export { - default as DropZone, - useDropZone as __unstableUseDropZone, -} from './drop-zone'; -export { - default as DropZoneProvider, - DropZoneContextProvider as __unstableDropZoneContextProvider, - useDrop as __unstableUseDrop, -} from './drop-zone/provider'; +export { default as DropZone } from './drop-zone'; +export { default as DropZoneProvider } from './drop-zone/provider'; export { default as Dropdown } from './dropdown'; export { default as DropdownMenu } from './dropdown-menu'; export { default as ExternalLink } from './external-link'; diff --git a/packages/compose/src/hooks/use-drop-zone/index.js b/packages/compose/src/hooks/use-drop-zone/index.js new file mode 100644 index 0000000000000..bbfc0ba461a89 --- /dev/null +++ b/packages/compose/src/hooks/use-drop-zone/index.js @@ -0,0 +1,181 @@ +/** + * WordPress dependencies + */ +import { useRef } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import useRefEffect from '../use-ref-effect'; + +/** @typedef {import('@wordpress/element').RefCallback} RefCallback */ + +function useFreshRef( value ) { + const ref = useRef(); + ref.current = value; + return ref; +} + +/** + * A hook to facilitate drag and drop handling. + * + * @param {Object} $1 Named parameters. + * @param {boolean} $1.isDisabled Whether or not to disable the drop zone. + * @param {DragEvent} $1.onDragStart Called when dragging has started. + * @param {DragEvent} $1.onDragEnter Called when the zone is entered. + * @param {DragEvent} $1.onDragOver Called when the zone is moved within. + * @param {DragEvent} $1.onDragLeave Called when the zone is left. + * @param {MouseEvent} $1.onDragEnd Called when dragging has ended. + * @param {DragEvent} $1.onDrop Called when dropping in the zone. + * + * @return {RefCallback} Ref callback to be passed to the drop zone element. + */ +export default function useDropZone( { + isDisabled, + onDrop: _onDrop, + onDragStart: _onDragStart, + onDragEnter: _onDragEnter, + onDragLeave: _onDragLeave, + onDragEnd: _onDragEnd, + onDragOver: _onDragOver, +} ) { + const onDropRef = useFreshRef( _onDrop ); + const onDragStartRef = useFreshRef( _onDragStart ); + const onDragEnterRef = useFreshRef( _onDragEnter ); + const onDragLeaveRef = useFreshRef( _onDragLeave ); + const onDragEndRef = useFreshRef( _onDragEnd ); + const onDragOverRef = useFreshRef( _onDragOver ); + + return useRefEffect( + ( element ) => { + if ( isDisabled ) { + return; + } + + let isDragging = false; + + const { ownerDocument } = element; + + function maybeDragStart( event ) { + if ( isDragging ) { + return; + } + + isDragging = true; + + ownerDocument.removeEventListener( + 'dragenter', + maybeDragStart + ); + + if ( onDragStartRef.current ) { + onDragStartRef.current( event ); + } + } + + function onDragEnter( event ) { + event.preventDefault(); + + // The `dragenter` event will also fire when entering child + // elements, but we only want to call `onDragEnter` when + // entering the drop zone, which means the `relatedTarget` + // (element that has been left) should be outside the drop zone. + if ( element.contains( event.relatedTarget ) ) { + return; + } + + if ( onDragEnterRef.current ) { + onDragEnterRef.current( event ); + } + } + + function onDragOver( event ) { + // Only call onDragOver for the innermost hovered drop zones. + if ( ! event.defaultPrevented && onDragOverRef.current ) { + onDragOverRef.current( event ); + } + + // Prevent the browser default while also signalling to parent + // drop zones that `onDragOver` is already handled. + event.preventDefault(); + } + + function onDragLeave( event ) { + // The `dragleave` event will also fire when leaving child + // elements, but we only want to call `onDragLeave` when + // leaving the drop zone, which means the `relatedTarget` + // (element that has been entered) should be outside the drop + // zone. + if ( element.contains( event.relatedTarget ) ) { + return; + } + + if ( onDragLeaveRef.current ) { + onDragLeaveRef.current( event ); + } + } + + function onDrop( event ) { + // Don't handle drop if an inner drop zone already handled it. + if ( event.defaultPrevented ) { + return; + } + + // Prevent the browser default while also signalling to parent + // drop zones that `onDrop` is already handled. + event.preventDefault(); + + // This seemingly useless line has been shown to resolve a + // Safari issue where files dragged directly from the dock are + // not recognized. + // eslint-disable-next-line no-unused-expressions + event.dataTransfer && event.dataTransfer.files.length; + + if ( onDropRef.current ) { + onDropRef.current( event ); + } + + maybeDragEnd( event ); + } + + function maybeDragEnd( event ) { + if ( ! isDragging ) { + return; + } + + isDragging = false; + + ownerDocument.addEventListener( 'dragenter', maybeDragStart ); + + if ( onDragEndRef.current ) { + onDragEndRef.current( event ); + } + } + + element.addEventListener( 'drop', onDrop ); + element.addEventListener( 'dragenter', onDragEnter ); + element.addEventListener( 'dragover', onDragOver ); + element.addEventListener( 'dragleave', onDragLeave ); + // Note that `dragend` doesn't fire consistently for file and HTML + // drag events where the drag origin is outside the browser window. + // In Firefox it may also not fire if the originating node is + // removed. + ownerDocument.addEventListener( 'dragend', maybeDragEnd ); + ownerDocument.addEventListener( 'mouseup', maybeDragEnd ); + // The `dragstart` event doesn't fire if the drag started outside + // the document. + ownerDocument.addEventListener( 'dragenter', maybeDragStart ); + + return () => { + element.removeEventListener( 'drop', onDrop ); + element.removeEventListener( 'dragenter', onDragEnter ); + element.removeEventListener( 'dragover', onDragOver ); + element.removeEventListener( 'dragleave', onDragLeave ); + ownerDocument.removeEventListener( 'dragend', maybeDragEnd ); + ownerDocument.removeEventListener( 'mouseup', maybeDragEnd ); + ownerDocument.addEventListener( 'dragenter', maybeDragStart ); + }; + }, + [ isDisabled ] + ); +} diff --git a/packages/compose/src/index.js b/packages/compose/src/index.js index 5a42d7f19d8c1..b49d8f5912cf0 100644 --- a/packages/compose/src/index.js +++ b/packages/compose/src/index.js @@ -35,3 +35,4 @@ export { default as useDebounce } from './hooks/use-debounce'; export { default as useThrottle } from './hooks/use-throttle'; export { default as useMergeRefs } from './hooks/use-merge-refs'; export { default as useRefEffect } from './hooks/use-ref-effect'; +export { default as __experimentalUseDropZone } from './hooks/use-drop-zone'; diff --git a/packages/customize-widgets/src/components/sidebar-block-editor/index.js b/packages/customize-widgets/src/components/sidebar-block-editor/index.js index 472509419e88e..a5befcf9cb5a3 100644 --- a/packages/customize-widgets/src/components/sidebar-block-editor/index.js +++ b/packages/customize-widgets/src/components/sidebar-block-editor/index.js @@ -11,11 +11,7 @@ import { BlockEditorKeyboardShortcuts, __experimentalBlockSettingsMenuFirstItem, } from '@wordpress/block-editor'; -import { - DropZoneProvider, - SlotFillProvider, - Popover, -} from '@wordpress/components'; +import { SlotFillProvider, Popover } from '@wordpress/components'; /** * Internal dependencies @@ -41,54 +37,52 @@ export default function SidebarBlockEditor( { sidebar, inserter, inspector } ) { <> - - - + + -
+
- - - - - - - - + + + + + + + + - + - { createPortal( - // This is a temporary hack to prevent button component inside - // from submitting form when type="button" is not specified. -
event.preventDefault() }> - - , - inspector.contentContainer[ 0 ] - ) } + { createPortal( + // This is a temporary hack to prevent button component inside + // from submitting form when type="button" is not specified. +
event.preventDefault() }> + + , + inspector.contentContainer[ 0 ] + ) } - <__experimentalBlockSettingsMenuFirstItem> - { ( { onClose } ) => ( - - ) } - + <__experimentalBlockSettingsMenuFirstItem> + { ( { onClose } ) => ( + + ) } + - { - // We have to portal this to the parent of both the editor and the inspector, - // so that the popovers will appear above both of them. - createPortal( , parentContainer ) - } - + { + // We have to portal this to the parent of both the editor and the inspector, + // so that the popovers will appear above both of them. + createPortal( , parentContainer ) + } ); diff --git a/packages/e2e-tests/specs/editor/various/draggable-block.test.js b/packages/e2e-tests/specs/editor/various/draggable-block.test.js index 2d0ebc0bb8974..7541f57050cef 100644 --- a/packages/e2e-tests/specs/editor/various/draggable-block.test.js +++ b/packages/e2e-tests/specs/editor/various/draggable-block.test.js @@ -48,7 +48,7 @@ describe( 'Draggable block', () => { await page.evaluate( () => { document.addEventListener( 'dragstart', ( event ) => { window._dataTransfer = JSON.parse( - event.dataTransfer.getData( 'text' ) + event.dataTransfer.getData( 'wp-blocks' ) ); } ); } ); @@ -76,7 +76,7 @@ describe( 'Draggable block', () => { ( element, clientX, clientY ) => { const dataTransfer = new DataTransfer(); dataTransfer.setData( - 'text/plain', + 'wp-blocks', JSON.stringify( window._dataTransfer ) ); const event = new DragEvent( 'drop', { diff --git a/packages/edit-navigation/src/components/layout/index.js b/packages/edit-navigation/src/components/layout/index.js index 4073abd9cdb0a..304657989176c 100644 --- a/packages/edit-navigation/src/components/layout/index.js +++ b/packages/edit-navigation/src/components/layout/index.js @@ -11,12 +11,7 @@ import { BlockEditorProvider, __unstableUseBlockSelectionClearer as useBlockSelectionClearer, } from '@wordpress/block-editor'; -import { - DropZoneProvider, - Popover, - SlotFillProvider, - Spinner, -} from '@wordpress/components'; +import { Popover, SlotFillProvider, Spinner } from '@wordpress/components'; import { useViewportMatch } from '@wordpress/compose'; import { useDispatch, useSelect } from '@wordpress/data'; import { useEffect, useMemo, useState } from '@wordpress/element'; @@ -127,126 +122,122 @@ export default function Layout( { blockEditorSettings } ) { className={ 'edit-navigation-layout__overlay' } /> - - - - - - - - [ - isMenuNameControlFocused, - setIsMenuNameControlFocused, - ], - [ isMenuNameControlFocused ] - ) } - > - + + + + + + + [ + isMenuNameControlFocused, + setIsMenuNameControlFocused, + ], + [ isMenuNameControlFocused ] + ) } + > + - { ! hasFinishedInitialLoad && ( - - ) } + ) } + labels={ interfaceLabels } + header={ +
+ } + content={ + <> + { ! hasFinishedInitialLoad && ( + + ) } - { ! isMenuSelected && - hasFinishedInitialLoad && ( - + ) } + { isBlockEditorReady && ( + <> + +
+ + - ) } - { isBlockEditorReady && ( - <> - -
- - -
- - ) } - - } - sidebar={ - ( hasPermanentSidebar || - hasSidebarEnabled ) && ( - - ) - } - /> - - - - - - +
+ + ) } + + } + sidebar={ + ( hasPermanentSidebar || + hasSidebarEnabled ) && ( + + ) + } + /> + + + + + ); diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index e65447be8cb6b..3bbee2c99851b 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -18,12 +18,7 @@ import { BlockBreadcrumb, __experimentalLibrary as Library, } from '@wordpress/block-editor'; -import { - Button, - ScrollLock, - Popover, - __unstableUseDrop as useDrop, -} from '@wordpress/components'; +import { Button, ScrollLock, Popover } from '@wordpress/components'; import { useViewportMatch, __experimentalUseDialog as useDialog, @@ -172,7 +167,6 @@ function Layout( { styles } ) { }, [ entitiesSavedStatesCallback ] ); - const ref = useDrop( ref ); const [ inserterDialogRef, inserterDialogProps ] = useDialog( { onClose: () => setIsInserterOpened( false ), } ); @@ -188,7 +182,6 @@ function Layout( { styles } ) { - - - - - - - - - - + + + + + + + + diff --git a/packages/edit-site/src/components/block-editor/index.js b/packages/edit-site/src/components/block-editor/index.js index a119fc490f876..10887d3127590 100644 --- a/packages/edit-site/src/components/block-editor/index.js +++ b/packages/edit-site/src/components/block-editor/index.js @@ -18,7 +18,7 @@ import { __unstableEditorStyles as EditorStyles, __unstableIframe as Iframe, } from '@wordpress/block-editor'; -import { DropZoneProvider, Popover } from '@wordpress/components'; +import { Popover } from '@wordpress/components'; import { useMergeRefs } from '@wordpress/compose'; /** @@ -101,18 +101,16 @@ export default function BlockEditor( { setIsInserterOpen } ) { ref={ ref } contentRef={ mergedRefs } > - - - - - + + +
diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index 248bad7fb3a18..65701054c9f23 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -3,12 +3,7 @@ */ import { useEffect, useState, useMemo, useCallback } from '@wordpress/element'; import { AsyncModeProvider, useSelect, useDispatch } from '@wordpress/data'; -import { - SlotFillProvider, - DropZoneProvider, - Popover, - Button, -} from '@wordpress/components'; +import { SlotFillProvider, Popover, Button } from '@wordpress/components'; import { EntityProvider, store as coreStore } from '@wordpress/core-data'; import { BlockContextProvider, BlockBreadcrumb } from '@wordpress/block-editor'; import { @@ -175,100 +170,98 @@ function Editor( { initialSettings } ) { - - + + - - - + + + + } + secondarySidebar={ secondarySidebar() } + sidebar={ + sidebarIsOpened && ( + + ) + } + header={ +
+ } + content={ + <> + + { template && ( + + ) } + + } - > - - - } - secondarySidebar={ secondarySidebar() } - sidebar={ - sidebarIsOpened && ( - - ) - } - header={ -
+ - } - content={ - <> - - { template && ( - + -
- ) } - - } - footer={ } - /> - - - - - + aria-expanded={ + false + } + > + { __( + 'Open save panel' + ) } + +
+ ) } + + } + footer={ } + /> + + + + - + ); diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js index 83dc63822849a..632fcd362a900 100644 --- a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js +++ b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js @@ -6,7 +6,7 @@ import { defaultTo } from 'lodash'; /** * WordPress dependencies */ -import { DropZoneProvider, SlotFillProvider } from '@wordpress/components'; +import { SlotFillProvider } from '@wordpress/components'; import { uploadMedia } from '@wordpress/media-utils'; import { useDispatch, useSelect } from '@wordpress/data'; import { useMemo } from '@wordpress/element'; @@ -85,21 +85,17 @@ export default function WidgetAreasBlockEditorProvider( { - - - { children } - - - + + { children } + + ); diff --git a/storybook/stories/playground/index.js b/storybook/stories/playground/index.js index 8bd3adfab9eeb..d5e0a67623ab2 100644 --- a/storybook/stories/playground/index.js +++ b/storybook/stories/playground/index.js @@ -10,11 +10,7 @@ import { WritingFlow, ObserveTyping, } from '@wordpress/block-editor'; -import { - Popover, - SlotFillProvider, - DropZoneProvider, -} from '@wordpress/components'; +import { Popover, SlotFillProvider } from '@wordpress/components'; import { registerCoreBlocks } from '@wordpress/block-library'; import '@wordpress/format-library'; @@ -33,28 +29,26 @@ function App() { return (
- - -
- -
-
- - - - - - - - -
- -
-
+ +
+ +
+
+ + + + + + + + +
+ +
); From 619b2fe3f8fab42e5b0ce8d3ab4f9a9b0e64ab1a Mon Sep 17 00:00:00 2001 From: sarayourfriend Date: Mon, 19 Apr 2021 06:26:01 -0700 Subject: [PATCH 07/20] dom: Type the remaining files (#30841) * dom: Type the remaining files * Wide fn signature to avoid type casts * Remove duplicate dependencies comment block * Remove vertical alignment --- packages/dom/CHANGELOG.md | 4 +++ packages/dom/README.md | 4 +-- packages/dom/package.json | 1 + .../dom/document-has-uncollapsed-selection.js | 3 +- packages/dom/src/dom/get-offset-parent.js | 13 ++++--- .../input-field-has-uncollapsed-selection.js | 2 +- packages/dom/src/dom/is-edge.js | 8 ++--- packages/dom/src/dom/is-entirely-selected.js | 14 +++++--- packages/dom/tsconfig.json | 36 +------------------ 9 files changed, 33 insertions(+), 52 deletions(-) diff --git a/packages/dom/CHANGELOG.md b/packages/dom/CHANGELOG.md index b1d1f0d7d7205..1e12d0d2f3ea2 100644 --- a/packages/dom/CHANGELOG.md +++ b/packages/dom/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### New Feature + +- Export type definitions + ## 2.17.0 (2021-03-17) ## 2.11.0 (2020-06-15) diff --git a/packages/dom/README.md b/packages/dom/README.md index c6b0cb07cba20..0b9b5d37ef88c 100644 --- a/packages/dom/README.md +++ b/packages/dom/README.md @@ -102,7 +102,7 @@ _Parameters_ _Returns_ -- `?Node`: Offset parent. +- `Node | null`: Offset parent. # **getPhrasingContentSchema** @@ -178,7 +178,7 @@ Returns true if there is no possibility of selection. _Parameters_ -- _element_ `Element`: The element to check. +- _element_ `HTMLElement`: The element to check. _Returns_ diff --git a/packages/dom/package.json b/packages/dom/package.json index ac88871beb476..3cbacb0657230 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -22,6 +22,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "types": "build-types", "sideEffects": false, "dependencies": { "@babel/runtime": "^7.13.10", diff --git a/packages/dom/src/dom/document-has-uncollapsed-selection.js b/packages/dom/src/dom/document-has-uncollapsed-selection.js index 7cb68f00ec3ad..a0d7c38ccd91b 100644 --- a/packages/dom/src/dom/document-has-uncollapsed-selection.js +++ b/packages/dom/src/dom/document-has-uncollapsed-selection.js @@ -16,6 +16,7 @@ import inputFieldHasUncollapsedSelection from './input-field-has-uncollapsed-sel export default function documentHasUncollapsedSelection( doc ) { return ( documentHasTextSelection( doc ) || - inputFieldHasUncollapsedSelection( doc.activeElement ) + ( !! doc.activeElement && + inputFieldHasUncollapsedSelection( doc.activeElement ) ) ); } diff --git a/packages/dom/src/dom/get-offset-parent.js b/packages/dom/src/dom/get-offset-parent.js index 8310447c5b985..4606dbe4b93e5 100644 --- a/packages/dom/src/dom/get-offset-parent.js +++ b/packages/dom/src/dom/get-offset-parent.js @@ -12,13 +12,13 @@ import getComputedStyle from './get-computed-style'; * * @param {Node} node Node from which to find offset parent. * - * @return {?Node} Offset parent. + * @return {Node | null} Offset parent. */ export default function getOffsetParent( node ) { // Cannot retrieve computed style or offset parent only anything other than // an element node, so find the closest element node. let closestElement; - while ( ( closestElement = node.parentNode ) ) { + while ( ( closestElement = /** @type {Node} */ ( node.parentNode ) ) ) { if ( closestElement.nodeType === closestElement.ELEMENT_NODE ) { break; } @@ -30,9 +30,14 @@ export default function getOffsetParent( node ) { // If the closest element is already positioned, return it, as offsetParent // does not otherwise consider the node itself. - if ( getComputedStyle( closestElement ).position !== 'static' ) { + if ( + getComputedStyle( /** @type {Element} */ ( closestElement ) ) + .position !== 'static' + ) { return closestElement; } - return closestElement.offsetParent; + // offsetParent is undocumented/draft + return /** @type {Node & { offsetParent: Node }} */ ( closestElement ) + .offsetParent; } diff --git a/packages/dom/src/dom/input-field-has-uncollapsed-selection.js b/packages/dom/src/dom/input-field-has-uncollapsed-selection.js index 05f7fe1d2ab7f..ad974c8ce4298 100644 --- a/packages/dom/src/dom/input-field-has-uncollapsed-selection.js +++ b/packages/dom/src/dom/input-field-has-uncollapsed-selection.js @@ -13,7 +13,7 @@ import isNumberInput from './is-number-input'; * * See: https://developer.mozilla.org/en-US/docs/Web/API/Window/getSelection#Related_objects. * - * @param {HTMLElement} element The HTML element. + * @param {Element} element The HTML element. * * @return {boolean} Whether the input/textareaa element has some "selection". */ diff --git a/packages/dom/src/dom/is-edge.js b/packages/dom/src/dom/is-edge.js index 65dad21b19eb6..a8c576a05f2ad 100644 --- a/packages/dom/src/dom/is-edge.js +++ b/packages/dom/src/dom/is-edge.js @@ -14,13 +14,13 @@ import isInputOrTextArea from './is-input-or-text-area'; * horizontal position by default. Set `onlyVertical` to true to check only * vertically. * - * @param {Element} container Focusable element. - * @param {boolean} isReverse Set to true to check left, false to check right. - * @param {boolean} onlyVertical Set to true to check only vertical position. + * @param {Element} container Focusable element. + * @param {boolean} isReverse Set to true to check left, false to check right. + * @param {boolean} [onlyVertical=false] Set to true to check only vertical position. * * @return {boolean} True if at the edge, false if not. */ -export default function isEdge( container, isReverse, onlyVertical ) { +export default function isEdge( container, isReverse, onlyVertical = false ) { if ( isInputOrTextArea( container ) ) { if ( container.selectionStart !== container.selectionEnd ) { return false; diff --git a/packages/dom/src/dom/is-entirely-selected.js b/packages/dom/src/dom/is-entirely-selected.js index 05f6f656f5a52..3445f90facb31 100644 --- a/packages/dom/src/dom/is-entirely-selected.js +++ b/packages/dom/src/dom/is-entirely-selected.js @@ -1,18 +1,19 @@ /** - * External dependencies + * Internal dependencies */ -import { includes } from 'lodash'; +import { assertIsDefined } from '../utils/assert-is-defined'; +import isInputOrTextArea from './is-input-or-text-area'; /** * Check whether the contents of the element have been entirely selected. * Returns true if there is no possibility of selection. * - * @param {Element} element The element to check. + * @param {HTMLElement} element The element to check. * * @return {boolean} True if entirely selected, false if not. */ export default function isEntirelySelected( element ) { - if ( includes( [ 'INPUT', 'TEXTAREA' ], element.nodeName ) ) { + if ( isInputOrTextArea( element ) ) { return ( element.selectionStart === 0 && element.value.length === element.selectionEnd @@ -25,7 +26,9 @@ export default function isEntirelySelected( element ) { const { ownerDocument } = element; const { defaultView } = ownerDocument; + assertIsDefined( defaultView, 'defaultView' ); const selection = defaultView.getSelection(); + assertIsDefined( selection, 'selection' ); const range = selection.rangeCount ? selection.getRangeAt( 0 ) : null; if ( ! range ) { @@ -44,9 +47,10 @@ export default function isEntirelySelected( element ) { } const lastChild = element.lastChild; + assertIsDefined( lastChild, 'lastChild' ); const lastChildContentLength = lastChild.nodeType === lastChild.TEXT_NODE - ? lastChild.data.length + ? /** @type {Text} */ ( lastChild ).data.length : lastChild.childNodes.length; return ( diff --git a/packages/dom/tsconfig.json b/packages/dom/tsconfig.json index 8e47a4759dd01..6fa7b10e3f5be 100644 --- a/packages/dom/tsconfig.json +++ b/packages/dom/tsconfig.json @@ -6,40 +6,6 @@ "types": [ "gutenberg-env" ] }, "include": [ - "src/data-transfer.js", - "src/dom/caret-range-from-point.js", - "src/dom/clean-node-list.js", - "src/dom/compute-caret-rect.js", - "src/dom/document-has-selection.js", - "src/dom/document-has-text-selection.js", - "src/dom/get-computed-style.js", - "src/dom/get-range-height.js", - "src/dom/get-rectangle-from-range.js", - "src/dom/get-scroll-container.js", - "src/dom/hidden-caret-range-from-point.js", - "src/dom/input-field-has-uncollapsed-selection.js", - "src/dom/is-edge.js", - "src/dom/is-empty.js", - "src/dom/is-element.js", - "src/dom/is-html-input-element.js", - "src/dom/is-input-or-text-area.js", - "src/dom/is-number-input.js", - "src/dom/is-text-field.js", - "src/dom/is-selection-forward.js", - "src/dom/is-vertical-edge.js", - "src/dom/insert-after.js", - "src/dom/remove-invalid-html.js", - "src/dom/place-caret-at-horizontal-edge.js", - "src/dom/place-caret-at-vertical-edge.js", - "src/dom/remove.js", - "src/dom/strip-html.js", - "src/dom/replace-tag.js", - "src/dom/replace.js", - "src/dom/unwrap.js", - "src/dom/wrap.js", - "src/utils/**/*", - "src/focusable.js", - "src/phrasing-content.js", - "src/tabbable.js" + "src/**/*" ] } From fe379134644e6a8b43a220d1d6e956def9b5295e Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Mon, 19 Apr 2021 16:18:00 +0200 Subject: [PATCH 08/20] [RNMobile] Handle undefined block case in block actions menu (#30920) * Handle undefined block case in block actions menu * Change selectedBlock to be an object instead of array --- .../block-actions-menu.native.js | 17 +++++++++-------- .../block-transformations-menu.native.js | 3 +-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/block-mobile-toolbar/block-actions-menu.native.js b/packages/block-editor/src/components/block-mobile-toolbar/block-actions-menu.native.js index a5cc68adaf85e..0c2e3bfc82133 100644 --- a/packages/block-editor/src/components/block-mobile-toolbar/block-actions-menu.native.js +++ b/packages/block-editor/src/components/block-mobile-toolbar/block-actions-menu.native.js @@ -272,7 +272,7 @@ export default compose( const block = getBlock( normalizedClientIds ); const blockName = getBlockName( normalizedClientIds ); const blockType = getBlockType( blockName ); - const blockTitle = blockType.title; + const blockTitle = blockType?.title; const firstClientId = first( normalizedClientIds ); const rootClientId = getBlockRootClientId( firstClientId ); const blockOrder = getBlockOrder( rootClientId ); @@ -284,17 +284,18 @@ export default compose( ); const isDefaultBlock = blockName === getDefaultBlockName(); - const isEmptyContent = block.attributes.content === ''; + const isEmptyContent = block?.attributes.content === ''; const isExactlyOneBlock = blockOrder.length === 1; const isEmptyDefaultBlock = isExactlyOneBlock && isDefaultBlock && isEmptyContent; - const selectedBlockClientId = getSelectedBlockClientIds(); - const selectedBlock = getBlocksByClientId( selectedBlockClientId ); - const selectedBlockPossibleTransformations = getBlockTransformItems( - selectedBlock, - rootClientId - ); + const selectedBlockClientId = first( getSelectedBlockClientIds() ); + const selectedBlock = selectedBlockClientId + ? first( getBlocksByClientId( selectedBlockClientId ) ) + : undefined; + const selectedBlockPossibleTransformations = selectedBlock + ? getBlockTransformItems( [ selectedBlock ], rootClientId ) + : []; return { blockTitle, diff --git a/packages/block-editor/src/components/block-switcher/block-transformations-menu.native.js b/packages/block-editor/src/components/block-switcher/block-transformations-menu.native.js index ce2822d0c6dbc..fc91bdd6d37dc 100644 --- a/packages/block-editor/src/components/block-switcher/block-transformations-menu.native.js +++ b/packages/block-editor/src/components/block-switcher/block-transformations-menu.native.js @@ -29,8 +29,7 @@ const BlockTransformationsMenu = ( { const { createSuccessNotice } = useDispatch( noticesStore ); const pickerOptions = () => { - const selectedBlockName = - ( selectedBlock.length && selectedBlock[ 0 ].name ) || ''; + const selectedBlockName = selectedBlock?.name ?? ''; const blocksThatSplitWhenTransformed = { 'core/list': [ 'core/paragraph', 'core/heading' ], 'core/quote': [ 'core/paragraph' ], From b192bbab7f60242852158f3c706d953e2b36c0ef Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Mon, 19 Apr 2021 11:55:37 -0500 Subject: [PATCH 09/20] Add Cover block edit media integration tests (#30270) * Update @react-native-community/slider mock to accept refs Previously, running Jest unit tests outputted a React warning because the mock was overly simplified and could not accept the refs passed to it as it is a function component. * Add React Native testing library * Add Cover block integration test Add integration test for Cover block controls. * Fix lint warnings * Add Cover edit integration test Simple render test of Cover edit. * Add test with navigation * Add tests with and without attached media * Clean up integration test * Remove Cover Controls tests Superseded by Cover edit tests. * Remove unused import * Remove lingering debugging code * Fix React Native warning regarding passing numbers to TextInput * Add interactive edit focal point test via text input * Update Cover test to address act warngins * Avoid memory leaks from async state setting Update component to only set state if it is still "mounted." * Fix typo in spy name * Add fixed background toggle test * Add clear media test * Add discard focal point changes test * Rename tests * Add focal point slider test * Update snapshots with new react-native-modal mock Previously, the string 'Modal' was always rendered within the tree. The mock has been changed to conditionally render the Modal children so that we can test against the children elements. * Add additional context for mocked components * Create more robust react-native-modal mock Include the `Modal` element itself within the mock, rather than merely its children. * Replace unnecessary async queries to avoid RNTL bug The async queries were unnecessary. They also caused a bug within RNTL to output a lot of error in the test logs. https://git.io/JYYGE * Fix Image.getSize mock Image.getSize does not return a promise, but uses a callback instead. * Add passing tests skipped due to RNTL bug These tests pass, but output errors in the test log due to an open issue in RNTL. https://git.io/JYYGE * Only disable Cell a11y label when it is explicitly disabled Avoid erroneously removing an a11y label. * Clean up code and comments * Simplify Cover edit test file --- package-lock.json | 81 +++++ package.json | 1 + .../test/__snapshots__/edit.native.js.snap | 3 - .../src/cover/controls.native.js | 10 +- .../block-library/src/cover/edit.native.js | 39 ++- .../src/cover/test/edit.native.js | 283 ++++++++++++++++++ .../test/__snapshots__/edit.native.js.snap | 3 - .../test/__snapshots__/edit.native.js.snap | 1 - .../src/focal-point-picker/index.native.js | 4 +- .../tooltip/index.native.js | 2 +- .../src/mobile/bottom-sheet/cell.native.js | 13 +- .../mobile/bottom-sheet/range-cell.native.js | 1 + .../bottom-sheet/range-text-input.native.js | 5 +- .../src/mobile/image/index.native.js | 11 +- test/native/setup.js | 8 +- 15 files changed, 427 insertions(+), 38 deletions(-) create mode 100644 packages/block-library/src/cover/test/edit.native.js diff --git a/package-lock.json b/package-lock.json index 93558fe17b518..968f7655a8d27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11565,6 +11565,87 @@ "@testing-library/dom": "^7.28.1" } }, + "@testing-library/react-native": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@testing-library/react-native/-/react-native-7.1.0.tgz", + "integrity": "sha512-ljVM9KZqG7BT/NFN6CHzdF6MNmM28+k7MEybFJ7FW1wVrhpiY4+hU9ypZ+hboO+MG3KpE2aFBzP4od3gu+Zzdg==", + "dev": true, + "requires": { + "pretty-format": "^26.0.1" + }, + "dependencies": { + "@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + } + }, + "react-is": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", + "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", + "dev": true + } + } + }, "@types/anymatch": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", diff --git a/package.json b/package.json index fed5229fa8042..bfb7291ce44ab 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "@storybook/react": "6.2.5", "@testing-library/jest-dom": "5.11.9", "@testing-library/react": "11.2.2", + "@testing-library/react-native": "7.1.0", "@types/classnames": "2.2.10", "@types/eslint": "6.8.0", "@types/estree": "0.0.44", 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 34dff2838a0ac..da02296f2dc47 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 @@ -30,7 +30,6 @@ exports[`Audio block renders audio block error state without crashing 1`] = ` ] } /> - Modal - Modal - Modal ) } { VIDEO_BACKGROUND_TYPE === backgroundType && ( @@ -251,7 +251,7 @@ function Controls( { ); return ( - + <> - + ); } diff --git a/packages/block-library/src/cover/edit.native.js b/packages/block-library/src/cover/edit.native.js index 6b5afe66ff128..721b988b01b86 100644 --- a/packages/block-library/src/cover/edit.native.js +++ b/packages/block-library/src/cover/edit.native.js @@ -107,6 +107,8 @@ const Cover = ( { ); useEffect( () => { + let isCurrent = true; + // sync with local media store mediaUploadSync(); AccessibilityInfo.addEventListener( @@ -114,11 +116,14 @@ const Cover = ( { setIsScreenReaderEnabled ); - AccessibilityInfo.isScreenReaderEnabled().then( - setIsScreenReaderEnabled - ); + AccessibilityInfo.isScreenReaderEnabled().then( () => { + if ( isCurrent ) { + setIsScreenReaderEnabled(); + } + } ); return () => { + isCurrent = false; AccessibilityInfo.removeEventListener( 'screenReaderChanged', setIsScreenReaderEnabled @@ -246,7 +251,7 @@ const Cover = ( { customOverlayColor || overlayColor?.color || style?.color?.background || - styles.overlay.color, + styles.overlay?.color, }, // While we don't support theme colors we add a default bg color ! overlayColor.color && ! url ? backgroundColor : {}, @@ -400,7 +405,7 @@ const Cover = ( { onSelectMediaUploadOption={ onSelectMedia } openMediaOptions={ openMediaOptionsRef.current } url={ url } - width={ styles.image.width } + width={ styles.image?.width } /> ) } @@ -430,7 +435,9 @@ const Cover = ( { { isCustomColorPickerShowing && colorPickerControls } { isSelected && ( - + + + ) } ( { + ...jest.requireActual( '@wordpress/compose' ), + withPreferredColorScheme: jest.fn( ( Component ) => ( props ) => ( + ( {} ) ) } + /> + ) ), +} ) ); + +// Simplified tree to render Cover edit within slot +const CoverEdit = ( props ) => ( + + + + +); + +const setAttributes = jest.fn(); +const attributes = { + backgroundType: IMAGE_BACKGROUND_TYPE, + focalPoint: { x: '0.25', y: '0.75' }, + hasParallax: false, + overlayColor: { color: '#000000' }, + url: 'mock-url', +}; + +const isScreenReaderEnabled = Promise.resolve( true ); +beforeAll( () => { + // Mock Image.getSize to avoid failed attempt to size non-existant image + const getSizeSpy = jest.spyOn( Image, 'getSize' ); + getSizeSpy.mockImplementation( ( _url, callback ) => callback( 300, 200 ) ); + + // Mock async native module to avoid act warning + AccessibilityInfo.isScreenReaderEnabled = jest.fn( + () => isScreenReaderEnabled + ); + + // Register required blocks + registerBlockType( name, { + ...metadata, + ...settings, + } ); + registerBlockType( 'core/paragraph', { + category: 'text', + title: 'Paragraph', + edit: () => {}, + save: () => {}, + } ); +} ); + +afterAll( () => { + // Restore mocks + Image.getSize.mockRestore(); + + // Clean up registered blocks + unregisterBlockType( name ); + unregisterBlockType( 'core/paragraph' ); +} ); + +describe( 'when no media is attached', () => { + it( 'adds an image or video', async () => { + const { getByText, findByText } = render( + + ); + fireEvent.press( getByText( 'Add image or video' ) ); + const mediaLibraryButton = await findByText( + 'WordPress Media Library' + ); + fireEvent.press( mediaLibraryButton ); + + expect( requestMediaPicker ).toHaveBeenCalled(); + } ); +} ); + +describe( 'when an image is attached', () => { + // The skipped tests below pass but are currently skipped because multiple + // async findBy* queries currently cause errors in test output + // https://git.io/JYYGE + + // eslint-disable-next-line jest/no-disabled-tests + it.skip( 'edits the image', async () => { + const { getByLabelText, findByText } = render( + + ); + // Await async update to component state to avoid act warning + await act( () => isScreenReaderEnabled ); + fireEvent.press( getByLabelText( 'Edit image' ) ); + const editButton = await findByText( 'Edit' ); + fireEvent.press( editButton ); + + expect( requestMediaEditor ).toHaveBeenCalled(); + } ); + + // eslint-disable-next-line jest/no-disabled-tests + it.skip( 'replaces the image', async () => { + const { getByLabelText, findByText } = render( + + ); + // Await async update to component state to avoid act warning + await act( () => isScreenReaderEnabled ); + fireEvent.press( getByLabelText( 'Edit image' ) ); + const replaceButton = await findByText( 'Replace' ); + fireEvent.press( replaceButton ); + const mediaLibraryButton = await findByText( + 'WordPress Media Library' + ); + fireEvent.press( mediaLibraryButton ); + + expect( requestMediaPicker ).toHaveBeenCalled(); + } ); + + // eslint-disable-next-line jest/no-disabled-tests + it.skip( 'clears the image within image edit button', async () => { + const { getByLabelText, findAllByText } = render( + + ); + // Await async update to component state to avoid act warning + await act( () => isScreenReaderEnabled ); + fireEvent.press( getByLabelText( 'Edit image' ) ); + const clearMediaButton = await findAllByText( 'Clear Media' ); + fireEvent.press( clearMediaButton[ 0 ] ); + + expect( setAttributes ).toHaveBeenCalledWith( + expect.objectContaining( { + focalPoint: undefined, + hasParallax: undefined, + id: undefined, + url: undefined, + } ) + ); + } ); + + it( 'toggles a fixed background', async () => { + const { getByText } = render( + + ); + // Await async update to component state to avoid act warning + await act( () => isScreenReaderEnabled ); + fireEvent.press( getByText( 'Fixed background' ) ); + + expect( setAttributes ).toHaveBeenCalledWith( + expect.objectContaining( { + hasParallax: ! attributes.hasParallax, + } ) + ); + } ); + + it( 'edits the focal point with a slider', async () => { + const { getByText, getByLabelText, getByTestId } = render( + + ); + // Await async update to component state to avoid act warning + await act( () => isScreenReaderEnabled ); + fireEvent.press( getByText( 'Edit focal point' ) ); + fireEvent( + getByTestId( 'Slider Y-Axis Position' ), + 'valueChange', + '52' + ); + fireEvent.press( getByLabelText( 'Apply' ) ); + + expect( setAttributes ).toHaveBeenCalledWith( + expect.objectContaining( { + focalPoint: { ...attributes.focalPoint, y: '0.52' }, + } ) + ); + } ); + + it( 'edits the focal point with a text input', async () => { + const { getByText, getByLabelText } = render( + + ); + // Await async update to component state to avoid act warning + await act( () => isScreenReaderEnabled ); + fireEvent.press( getByText( 'Edit focal point' ) ); + fireEvent.press( + getByText( ( attributes.focalPoint.x * 100 ).toString() ) + ); + fireEvent.changeText( getByLabelText( 'X-Axis Position' ), '99' ); + fireEvent.press( getByLabelText( 'Apply' ) ); + + expect( setAttributes ).toHaveBeenCalledWith( + expect.objectContaining( { + focalPoint: { ...attributes.focalPoint, x: '0.99' }, + } ) + ); + } ); + + it( 'discards canceled focal point changes', async () => { + const { getByText, getByLabelText } = render( + + ); + // Await async update to component state to avoid act warning + await act( () => isScreenReaderEnabled ); + fireEvent.press( getByText( 'Edit focal point' ) ); + fireEvent.press( + getByText( ( attributes.focalPoint.x * 100 ).toString() ) + ); + fireEvent.changeText( getByLabelText( 'X-Axis Position' ), '80' ); + fireEvent.press( getByLabelText( 'Go back' ) ); + + expect( setAttributes ).not.toHaveBeenCalledWith( + expect.objectContaining( { + focalPoint: { ...attributes.focalPoint, x: '0.80' }, + } ) + ); + } ); + + it( 'clears the media within cell button', async () => { + const { getByText } = render( + + ); + // Await async update to component state to avoid act warning + await act( () => isScreenReaderEnabled ); + fireEvent.press( getByText( 'Clear Media' ) ); + + expect( setAttributes ).toHaveBeenCalledWith( + expect.objectContaining( { + focalPoint: undefined, + hasParallax: undefined, + id: undefined, + url: undefined, + } ) + ); + } ); +} ); diff --git a/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap index c762a7299bfa9..36a2e8b87915d 100644 --- a/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/file/test/__snapshots__/edit.native.js.snap @@ -41,7 +41,6 @@ exports[`File block renders file error state without crashing 1`] = ` > - Modal - Modal - Modal Unsupported - Modal `; diff --git a/packages/components/src/focal-point-picker/index.native.js b/packages/components/src/focal-point-picker/index.native.js index f48701a57a482..ba81919c1a054 100644 --- a/packages/components/src/focal-point-picker/index.native.js +++ b/packages/components/src/focal-point-picker/index.native.js @@ -243,9 +243,9 @@ function FocalPointPicker( props ) { yOffset={ -( FOCAL_POINT_SIZE / 2 ) } /> ) } diff --git a/packages/components/src/focal-point-picker/tooltip/index.native.js b/packages/components/src/focal-point-picker/tooltip/index.native.js index 8938233a0baf4..233f374416424 100644 --- a/packages/components/src/focal-point-picker/tooltip/index.native.js +++ b/packages/components/src/focal-point-picker/tooltip/index.native.js @@ -95,7 +95,7 @@ function Label( { align, text, xOffset, yOffset } ) { const tooltipStyles = [ styles.tooltip, { - shadowColor: styles.tooltipShadow.color, + shadowColor: styles.tooltipShadow?.color, shadowOffset: { width: 0, height: 2, diff --git a/packages/components/src/mobile/bottom-sheet/cell.native.js b/packages/components/src/mobile/bottom-sheet/cell.native.js index adf40c4d81881..4734eb45a05d6 100644 --- a/packages/components/src/mobile/bottom-sheet/cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/cell.native.js @@ -25,7 +25,7 @@ import { withPreferredColorScheme } from '@wordpress/compose'; */ import styles from './styles.scss'; import platformStyles from './cellStyles.scss'; -import TouchableRipple from './ripple.native.js'; +import TouchableRipple from './ripple'; class BottomSheetCell extends Component { constructor( props ) { @@ -38,6 +38,8 @@ class BottomSheetCell extends Component { this.handleScreenReaderToggled = this.handleScreenReaderToggled.bind( this ); + + this.isCurrent = false; } componentDidUpdate( prevProps, prevState ) { @@ -47,6 +49,7 @@ class BottomSheetCell extends Component { } componentDidMount() { + this.isCurrent = true; AccessibilityInfo.addEventListener( 'screenReaderChanged', this.handleScreenReaderToggled @@ -54,12 +57,15 @@ class BottomSheetCell extends Component { AccessibilityInfo.isScreenReaderEnabled().then( ( isScreenReaderEnabled ) => { - this.setState( { isScreenReaderEnabled } ); + if ( this.isCurrent ) { + this.setState( { isScreenReaderEnabled } ); + } } ); } componentWillUnmount() { + this.isCurrent = false; AccessibilityInfo.removeEventListener( 'screenReaderChanged', this.handleScreenReaderToggled @@ -249,6 +255,9 @@ class BottomSheetCell extends Component { }; const getAccessibilityLabel = () => { + if ( accessible === false ) { + return; + } if ( accessibilityLabel || ! showValue ) { return accessibilityLabel || label; } 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 49931aa74bcdc..24e20d49a0f61 100644 --- a/packages/components/src/mobile/bottom-sheet/range-cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/range-cell.native.js @@ -214,6 +214,7 @@ class BottomSheetRangeCell extends Component { { preview } { isIOS || hasFocus ? ( ( this._valueTextInput = c ) } style={ valueFinalStyle } onChangeText={ this.onChangeText } @@ -218,7 +219,7 @@ class RangeTextInput extends Component { returnKeyType="done" numberOfLines={ 1 } defaultValue={ `${ inputValue }` } - value={ inputValue } + value={ inputValue.toString() } pointerEvents={ hasFocus ? 'auto' : 'none' } /> ) : ( diff --git a/packages/components/src/mobile/image/index.native.js b/packages/components/src/mobile/image/index.native.js index 3116426526cba..6126b954a05e5 100644 --- a/packages/components/src/mobile/image/index.native.js +++ b/packages/components/src/mobile/image/index.native.js @@ -54,8 +54,12 @@ const ImageComponent = ( { const [ containerSize, setContainerSize ] = useState( null ); useEffect( () => { + let isCurrent = true; if ( url ) { Image.getSize( url, ( imgWidth, imgHeight ) => { + if ( ! isCurrent ) { + return; + } const metaData = { aspectRatio: imgWidth / imgHeight, width: imgWidth, @@ -67,6 +71,7 @@ const ImageComponent = ( { } } ); } + return () => ( isCurrent = false ); }, [ url ] ); const onContainerLayout = ( event ) => { @@ -124,20 +129,20 @@ const ImageComponent = ( { const customWidth = imageData?.width < containerSize?.width ? imageData?.width - : styles.wide.width; + : styles.wide?.width; const imageContainerStyles = [ styles.imageContent, { width: - imageWidth === styles.wide.width || + imageWidth === styles.wide?.width || ( imageData && imageWidth > 0 && imageWidth < containerSize?.width ) ? imageWidth : customWidth, }, - resizeMode && { width: styles.wide.width }, + resizeMode && { width: styles.wide?.width }, focalPoint && styles.focalPointContainer, ]; diff --git a/test/native/setup.js b/test/native/setup.js index 9ef6952aa8020..abc27aacf1f1a 100644 --- a/test/native/setup.js +++ b/test/native/setup.js @@ -23,7 +23,9 @@ jest.mock( '@wordpress/element', () => { jest.mock( '@wordpress/react-native-bridge', () => { return { addEventListener: jest.fn(), + mediaUploadSync: jest.fn(), removeEventListener: jest.fn(), + requestFocalPointPickerTooltipShown: jest.fn( () => true ), subscribeParentGetHtml: jest.fn(), subscribeParentToggleHTMLMode: jest.fn(), subscribeSetTitle: jest.fn(), @@ -40,6 +42,7 @@ jest.mock( '@wordpress/react-native-bridge', () => { subscribeMediaUpload: jest.fn(), subscribeMediaSave: jest.fn(), getOtherMediaOptions: jest.fn(), + requestMediaEditor: jest.fn(), requestMediaPicker: jest.fn(), requestUnsupportedBlockFallback: jest.fn(), subscribeReplaceBlock: jest.fn(), @@ -61,7 +64,9 @@ jest.mock( 'react-native-dark-mode', () => { }; } ); -jest.mock( 'react-native-modal', () => () => 'Modal' ); +jest.mock( 'react-native-modal', () => ( props ) => + props.isVisible ? mockComponent( 'Modal' )( props ) : null +); jest.mock( 'react-native-hr', () => () => 'Hr' ); @@ -142,6 +147,7 @@ jest.mock( 'react-native/Libraries/Animated/src/NativeAnimatedHelper' ); jest.mock( 'react-native/Libraries/Components/TextInput/TextInputState' ); // Mock native modules incompatible with testing environment +jest.mock( 'react-native/Libraries/LayoutAnimation/LayoutAnimation' ); jest.mock( 'react-native/Libraries/Components/AccessibilityInfo/AccessibilityInfo', () => ( { From fd2a1385dc853e2cfe8a4d1207efc0c12669a8f5 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 20 Apr 2021 04:16:44 +1000 Subject: [PATCH 10/20] Pattern Categories Select: stop event propagation on Select onBlur to fix issue in iOS (#30717) Co-authored-by: Andrew Serong --- .../src/components/inserter/pattern-panel.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/block-editor/src/components/inserter/pattern-panel.js b/packages/block-editor/src/components/inserter/pattern-panel.js index db3cb48572f55..74c80e9a8b82e 100644 --- a/packages/block-editor/src/components/inserter/pattern-panel.js +++ b/packages/block-editor/src/components/inserter/pattern-panel.js @@ -43,6 +43,16 @@ function PatternInserterPanel( { ); }; + // In iOS-based mobile devices, the onBlur will fire when selecting an option + // from a Select element. To prevent closing the useDialog on iOS devices, we + // stop propagating the onBlur event if there is no relatedTarget, which means + // that the user most likely did not click on an element within the editor canvas. + const onBlur = ( event ) => { + if ( ! event?.relatedTarget ) { + event.stopPropagation(); + } + }; + return ( <>
@@ -52,6 +62,7 @@ function PatternInserterPanel( { hideLabelFromVision value={ selectedCategory.name } onChange={ onChangeSelect } + onBlur={ onBlur } options={ categoryOptions() } />
From 8d630b6caae7a6ad1c85188aaaf227b6904beb15 Mon Sep 17 00:00:00 2001 From: Maggie Date: Mon, 19 Apr 2021 20:24:07 +0200 Subject: [PATCH 11/20] Revisit the bundled block patterns (#29973) * added new block patterns * changed background colors to group block * Switch large text to use viewport width font sizes. * Make "Overseas" font size slightly smaller so it sits on one line. * renaming and pattern adjustments * linting * buttons block pattern * Remove URL links. * Adjust categories. * Ensure "Columns" category is registered. * alt text for images * added missing alt text * Revise patterns for wider theme support. Plus some minor code cleanup. * Replace images with optimized versions * Fix line height and block markup error. * Remove extra space from text. * Add "Three columns of text" to the columns category. * Update text for three columns pattern. * Set line height for three columns pattern. * Update copy for Offset Title direction. * Simplify the Two columns of text with offset heading pattern. * Add a small spacer to the "Three columns with images and text" pattern. * change color values to use hexadecimal * fixed syntax * Update images to the final WP.org CDN URLs * renamed patterns and unregistered duplicates * added context and domain to translations, escaped strings * unregistered all the old default patterns * escape attributes with esc_attr__ * escaped strings with html tags using wp_kses_post * linting * Update lib/block-patterns/media-text-arquitecture.php Co-authored-by: Kjell Reigstad * Add blockTypes prop for compatibility with #30469. * Add categories for uncategorized patterns. * Add code comment. * linting * more linting * updated snapshot related to 2 buttons block * fixed conflict * fixed adding patterns snapshot * removed IDs from gallery to fix e2e tests * removed unnecesary data attributes * removed unnecesary ids * changed ids to null Co-authored-by: Kjell Reigstad --- lib/block-patterns.php | 63 +++++++++++++++++ lib/block-patterns/heading.php | 16 +++++ lib/block-patterns/large-header-left.php | 33 +++++++++ .../large-header-text-button.php | 35 ++++++++++ .../media-text-arquitecture.php | 21 ++++++ lib/block-patterns/media-text-art.php | 21 ++++++ lib/block-patterns/media-text-nature.php | 28 ++++++++ lib/block-patterns/quote.php | 30 ++++++++ .../text-two-columns-title-offset.php | 53 ++++++++++++++ lib/block-patterns/text-two-columns-title.php | 31 +++++++++ lib/block-patterns/text-two-columns.php | 44 ++++++++++++ .../three-columns-media-text.php | 69 +++++++++++++++++++ lib/block-patterns/three-columns-text.php | 43 ++++++++++++ lib/block-patterns/three-images-gallery.php | 43 ++++++++++++ lib/block-patterns/two-buttons.php | 22 ++++++ lib/block-patterns/two-images-gallery.php | 15 ++++ .../adding-patterns.test.js.snap | 8 +-- 17 files changed, 571 insertions(+), 4 deletions(-) create mode 100644 lib/block-patterns/heading.php create mode 100644 lib/block-patterns/large-header-left.php create mode 100644 lib/block-patterns/large-header-text-button.php create mode 100644 lib/block-patterns/media-text-arquitecture.php create mode 100644 lib/block-patterns/media-text-art.php create mode 100644 lib/block-patterns/media-text-nature.php create mode 100644 lib/block-patterns/quote.php create mode 100644 lib/block-patterns/text-two-columns-title-offset.php create mode 100644 lib/block-patterns/text-two-columns-title.php create mode 100644 lib/block-patterns/text-two-columns.php create mode 100644 lib/block-patterns/three-columns-media-text.php create mode 100644 lib/block-patterns/three-columns-text.php create mode 100644 lib/block-patterns/three-images-gallery.php create mode 100644 lib/block-patterns/two-buttons.php create mode 100644 lib/block-patterns/two-images-gallery.php diff --git a/lib/block-patterns.php b/lib/block-patterns.php index 789f483aea6c3..3c3695d4ac049 100644 --- a/lib/block-patterns.php +++ b/lib/block-patterns.php @@ -159,6 +159,7 @@ 'paragraph/large-with-background-color', array( 'title' => __( 'Large Paragraph with background color', 'gutenberg' ), + 'categories' => array( 'Text' ), 'blockTypes' => array( 'core/paragraph' ), 'viewportWidth' => 500, 'content' => ' @@ -170,6 +171,7 @@ 'social-links/shared-background-color', array( 'title' => __( 'Social links with a shared background color', 'gutenberg' ), + 'categories' => array( 'Buttons' ), 'blockTypes' => array( 'core/social-links' ), 'viewportWidth' => 500, 'content' => ' @@ -179,3 +181,64 @@ ', ) ); + +// Deactivate the legacy patterns bundled with WordPress, and add new block patterns for testing. +// More details in the trac issue (https://core.trac.wordpress.org/ticket/52846). +add_action( + 'init', + function() { + + $core_block_patterns = array( + 'text-two-columns', + 'two-buttons', + 'two-images', + 'text-two-columns-with-images', + 'text-three-columns-buttons', + 'large-header', + 'large-header-button', + 'three-buttons', + 'heading-paragraph', + 'quote', + ); + + $new_core_block_patterns = array( + 'media-text-nature', + 'two-images-gallery', + 'three-columns-media-text', + 'quote', + 'large-header-left', + 'large-header-text-button', + 'media-text-art', + 'text-two-columns-title', + 'three-columns-text', + 'text-two-columns-title-offset', + 'heading', + 'three-images-gallery', + 'text-two-columns', + 'media-text-arquitecture', + 'two-buttons', + ); + + if ( ! function_exists( 'unregister_block_pattern' ) ) { + return; + } + + foreach ( $core_block_patterns as $core_block_pattern ) { + unregister_block_pattern( 'core/' . $core_block_pattern ); + } + + register_block_pattern_category( 'buttons', array( 'label' => _x( 'Buttons', 'Block pattern category', 'default' ) ) ); + register_block_pattern_category( 'columns', array( 'label' => _x( 'Columns', 'Block pattern category', 'default' ) ) ); + register_block_pattern_category( 'header', array( 'label' => _x( 'Headers', 'Block pattern category', 'default' ) ) ); + register_block_pattern_category( 'gallery', array( 'label' => _x( 'Gallery', 'Block pattern category', 'default' ) ) ); + register_block_pattern_category( 'text', array( 'label' => _x( 'Text', 'Block pattern category', 'default' ) ) ); + + foreach ( $new_core_block_patterns as $core_block_pattern ) { + register_block_pattern( + 'core/' . $core_block_pattern, + require __DIR__ . '/block-patterns/' . $core_block_pattern . '.php' + ); + } + + } +); diff --git a/lib/block-patterns/heading.php b/lib/block-patterns/heading.php new file mode 100644 index 0000000000000..571ff91fa36c4 --- /dev/null +++ b/lib/block-patterns/heading.php @@ -0,0 +1,16 @@ + _x( 'Heading', 'Block pattern title', 'default' ), + 'categories' => array( 'text' ), + 'blockTypes' => array( 'core/heading' ), + 'content' => ' +

' . esc_html__( "We're a studio in Berlin with an international practice in architecture, urban planning and interior design. We believe in sharing knowledge and promoting dialogue to increase the creative potential of collaboration.", 'default' ) . '

+ ', + 'description' => _x( 'Heading text', 'Block pattern description', 'default' ), +); diff --git a/lib/block-patterns/large-header-left.php b/lib/block-patterns/large-header-left.php new file mode 100644 index 0000000000000..6b2da58036007 --- /dev/null +++ b/lib/block-patterns/large-header-left.php @@ -0,0 +1,33 @@ + _x( 'Large header with left-aligned text', 'Block pattern title', 'default' ), + 'categories' => array( 'header' ), + 'content' => ' +
+

' . esc_html__( 'Forest.', 'default' ) . '

+ + + +
+
+ + + + +

' . esc_html__( 'Even a child knows how valuable the forest is. The fresh, breathtaking smell of trees. Echoing birds flying above that dense magnitude. A stable climate, a sustainable diverse life and a source of culture. Yet, forests and other ecosystems hang in the balance, threatened to become croplands, pasture, and plantations.', 'default' ) . '

+
+ + + +
+
+
+ ', + 'description' => _x( 'Cover image with quote on top', 'Block pattern description', 'default' ), +); diff --git a/lib/block-patterns/large-header-text-button.php b/lib/block-patterns/large-header-text-button.php new file mode 100644 index 0000000000000..6f7d4d9f5a411 --- /dev/null +++ b/lib/block-patterns/large-header-text-button.php @@ -0,0 +1,35 @@ + _x( 'Large header with text and a button.', 'Block pattern title', 'default' ), + 'categories' => array( 'header' ), + 'content' => ' +
+

' . esc_html__( 'Overseas:', 'default' ) . '
' . esc_html__( '1500 — 1960', 'default' ) . '

+ + + +
+
+

' . wp_kses_post( __( 'An exhibition about the different representations of the ocean throughout time, between the sixteenth and the twentieth century. Taking place in our Open Room in Floor 2.', 'default' ) ) . '

+ + + + +
+ + + +
+
+
+ ', + 'description' => _x( 'Large header with background image and text and button on top', 'Block pattern description', 'default' ), +); diff --git a/lib/block-patterns/media-text-arquitecture.php b/lib/block-patterns/media-text-arquitecture.php new file mode 100644 index 0000000000000..ba7cf9521e7e5 --- /dev/null +++ b/lib/block-patterns/media-text-arquitecture.php @@ -0,0 +1,21 @@ + _x( 'Media and text with image on the right', 'Block pattern title', 'default' ), + 'categories' => array( 'header' ), + 'content' => ' +
' . esc_attr__( 'Close-up, abstract view of architecture.', 'default' ) . '
+

' . esc_html__( 'Open Spaces', 'default' ) . '

+ + + +

' . esc_html__( 'See case study ↗', 'default' ) . '

+
+ ', + 'description' => _x( 'Media and text block with image to the left and text to the right', 'Block pattern description', 'default' ), +); diff --git a/lib/block-patterns/media-text-art.php b/lib/block-patterns/media-text-art.php new file mode 100644 index 0000000000000..85ed71d41a6f1 --- /dev/null +++ b/lib/block-patterns/media-text-art.php @@ -0,0 +1,21 @@ + _x( 'Media & text with image on the right', 'Block pattern title', 'default' ), + 'categories' => array( 'header' ), + 'content' => ' +
' . esc_attr__( 'A green and brown rural landscape leading into a bright blue ocean and slightly cloudy sky, done in oil paints.', 'default' ) . '
+

' . esc_html__( 'Shore with Blue Sea', 'default' ) . '

+ + + +

' . esc_html__( 'Eleanor Harris (American, 1901-1942)', 'default' ) . '

+
+ ', + 'description' => _x( 'Media and text block with image to the right and text to the left', 'Block pattern description', 'default' ), +); diff --git a/lib/block-patterns/media-text-nature.php b/lib/block-patterns/media-text-nature.php new file mode 100644 index 0000000000000..e51f8514531b7 --- /dev/null +++ b/lib/block-patterns/media-text-nature.php @@ -0,0 +1,28 @@ + _x( 'Media & text in a full height container', 'Block pattern title', 'default' ), + 'categories' => array( 'header' ), + 'content' => ' +
+
' . esc_attr__( 'Close-up of dried, cracked earth.', 'default' ) . '
+

' . esc_html__( "What's the problem?", 'default' ) . '

+ + + +

' . esc_html__( 'Trees are more important today than ever before. More than 10,000 products are reportedly made from trees. Through chemistry, the humble woodpile is yielding chemicals, plastics and fabrics that were beyond comprehension when an axe first felled a Texas tree.', 'default' ) . '

+ + + +
+
+ ', + 'description' => _x( 'Media and text block with image to the left and text and button to the right', 'Block pattern description', 'default' ), +); diff --git a/lib/block-patterns/quote.php b/lib/block-patterns/quote.php new file mode 100644 index 0000000000000..bbb844472f05c --- /dev/null +++ b/lib/block-patterns/quote.php @@ -0,0 +1,30 @@ + _x( 'Quote', 'Block pattern title', 'default' ), + 'categories' => array( 'text' ), + 'blockTypes' => array( 'core/quote' ), + 'content' => ' +
+
+ + + +
' . esc_attr__( 'A side profile of a woman in a russet-colored turtleneck and white bag. She looks up with her eyes closed.', 'default' ) . '
+ + + +

' . esc_html__( "\"Contributing makes me feel like I'm being useful to the planet.\"", 'default' ) . '

' . wp_kses_post( __( '— Anna Wong, Volunteer', 'default' ) ) . '
+ + + +
+
+ ', + 'description' => _x( 'Testimonial quote with portrait', 'Block pattern description', 'default' ), +); diff --git a/lib/block-patterns/text-two-columns-title-offset.php b/lib/block-patterns/text-two-columns-title-offset.php new file mode 100644 index 0000000000000..e080234ef3af0 --- /dev/null +++ b/lib/block-patterns/text-two-columns-title-offset.php @@ -0,0 +1,53 @@ + _x( 'Two columns of text with offset heading', 'Block pattern title', 'default' ), + 'categories' => array( 'columns', 'text' ), + 'content' => ' +
+ + + + +
+
+

' . esc_html__( 'Oceanic Inspiration', 'default' ) . '

+
+ + + +
+
+
+
+ + + +
+
+ + + +
+

' . esc_html__( 'Winding veils round their heads, the women walked on deck. They were now moving steadily down the river, passing the dark shapes of ships at anchor, and London was a swarm of lights with a pale yellow canopy drooping above it. There were the lights of the great theatres, the lights of the long streets, lights that indicated huge squares of domestic comfort, lights that hung high in air.', 'default' ) . '

+
+ + + +
+

' . esc_html__( 'No darkness would ever settle upon those lamps, as no darkness had settled upon them for hundreds of years. It seemed dreadful that the town should blaze for ever in the same spot; dreadful at least to people going away to adventure upon the sea, and beholding it as a circumscribed mound, eternally burnt, eternally scarred. From the deck of the ship the great city appeared a crouched and cowardly figure, a sedentary miser.', 'default' ) . '

+
+
+ + + + +
+ ', + 'description' => _x( 'Two columns of text with offset heading', 'Block pattern description', 'default' ), +); diff --git a/lib/block-patterns/text-two-columns-title.php b/lib/block-patterns/text-two-columns-title.php new file mode 100644 index 0000000000000..c21f8f220ed50 --- /dev/null +++ b/lib/block-patterns/text-two-columns-title.php @@ -0,0 +1,31 @@ + _x( 'Two columns text and title', 'Block pattern title', 'default' ), + 'categories' => array( 'columns', 'text' ), + 'content' => ' +
+

' . esc_html__( 'The voyage had begun, and had begun happily with a soft blue sky, and a calm sea.', 'default' ) . '

+ + + +
+
+

' . esc_html__( 'They followed her on to the deck. All the smoke and the houses had disappeared, and the ship was out in a wide space of sea very fresh and clear though pale in the early light. They had left London sitting on its mud. A very thin line of shadow tapered on the horizon, scarcely thick enough to stand the burden of Paris, which nevertheless rested upon it. They were free of roads, free of mankind, and the same exhilaration at their freedom ran through them all.', 'default' ) . '

+
+ + + +
+

' . esc_html__( "The ship was making her way steadily through small waves which slapped her and then fizzled like effervescing water, leaving a little border of bubbles and foam on either side. The colourless October sky above was thinly clouded as if by the trail of wood-fire smoke, and the air was wonderfully salt and brisk. Indeed it was too cold to stand still. Mrs. Ambrose drew her arm within her husband's, and as they moved off it could be seen from the way in which her sloping cheek turned up to his that she had something private to communicate.", 'default' ) . '

+
+
+
+ ', + 'description' => _x( 'Two columns text and title', 'Block pattern description', 'default' ), +); diff --git a/lib/block-patterns/text-two-columns.php b/lib/block-patterns/text-two-columns.php new file mode 100644 index 0000000000000..7ab157d8ca1ae --- /dev/null +++ b/lib/block-patterns/text-two-columns.php @@ -0,0 +1,44 @@ + _x( 'Two columns of text', 'Block pattern title', 'default' ), + 'categories' => array( 'columns', 'text' ), + 'content' => ' +
+
+ + + +

' . esc_html__( 'We have worked with:', 'default' ) . '

+ + + +

' . wp_kses_post( __( 'EARTHFUND™
ARCHWEEKLY
FUTURE ROADS
BUILDING NY', 'default' ) ) . '

+ + + + +
+ + + + +
+ ', + 'description' => _x( 'Two columns of text', 'Block pattern description', 'default' ), +); diff --git a/lib/block-patterns/three-columns-media-text.php b/lib/block-patterns/three-columns-media-text.php new file mode 100644 index 0000000000000..e1636e451d98b --- /dev/null +++ b/lib/block-patterns/three-columns-media-text.php @@ -0,0 +1,69 @@ + _x( 'Three columns with images and text', 'Block pattern title', 'default' ), + 'categories' => array( 'columns' ), + 'content' => ' +
+
+
+ + + + +
' . esc_html__( 'ECOSYSTEM', 'default' ) . '
+ + + +

' . esc_html__( 'Positive growth.', 'default' ) . '

+ + +
+
+ + + +
+
+

' . wp_kses_post( __( 'Nature, in the common sense, refers to essences unchanged by man; space, the air, the river, the leaf. Art is applied to the mixture of his will with the same things, as in a house, a canal, a statue, a picture. But his operations taken together are so insignificant, a little chipping, baking, patching, and washing, that in an impression so grand as that of the world on the human mind, they do not vary the result.', 'default' ) ) . '

+
+ + + +
+ + + + +
' . esc_attr__( 'The sun setting through a dense forest of trees.', 'default' ) . '
+
+ + + +
+
' . esc_attr__( 'Wind turbines standing on a grassy plain, against a blue sky.', 'default' ) . '
+
+
+ + + +
+
+
' . esc_attr__( 'The sun shining over a ridge leading down into the shore. In the distance, a car drives down a road.', 'default' ) . '
+
+ + + +
+

' . esc_html__( "Undoubtedly we have no questions to ask which are unanswerable. We must trust the perfection of the creation so far, as to believe that whatever curiosity the order of things has awakened in our minds, the order of things can satisfy. Every man's condition is a solution in hieroglyphic to those inquiries he would put.", 'default' ) . '

+
+
+
+ ', + 'description' => _x( 'Three columns with images and text', 'Block pattern description', 'default' ), +); diff --git a/lib/block-patterns/three-columns-text.php b/lib/block-patterns/three-columns-text.php new file mode 100644 index 0000000000000..8d6b14b8ba467 --- /dev/null +++ b/lib/block-patterns/three-columns-text.php @@ -0,0 +1,43 @@ + _x( 'Three columns of text', 'Block pattern title', 'default' ), + 'categories' => array( 'columns', 'text' ), + 'content' => ' +
+
+

' . esc_html__( 'Virtual Tour ↗', 'default' ) . '

+ + + +

' . esc_html__( 'Get a virtual tour of the museum. Ideal for schools and events.', 'default' ) . '

+
+ + + +
+

' . esc_html__( 'Current Shows ↗', 'default' ) . '

+ + + +

' . esc_html__( 'Stay updated and see our current exhibitions here.', 'default' ) . '

+
+ + + +
+

' . esc_html__( 'Useful Info ↗', 'default' ) . '

+ + + +

' . esc_html__( 'Get to know our opening times, ticket prices and discounts.', 'default' ) . '

+
+
+ ', + 'description' => _x( 'Three columns of text', 'Block pattern description', 'default' ), +); diff --git a/lib/block-patterns/three-images-gallery.php b/lib/block-patterns/three-images-gallery.php new file mode 100644 index 0000000000000..8b5d9be1bbc4a --- /dev/null +++ b/lib/block-patterns/three-images-gallery.php @@ -0,0 +1,43 @@ + _x( 'Three columns with offset images', 'Block pattern title', 'default' ), + 'categories' => array( 'gallery' ), + 'content' => ' +
+
+
' . esc_attr__( 'Close-up, abstract view of geometric architecture.', 'default' ) . '
+
+ + + +
+ + + + + + + + +
' . esc_attr__( 'Close-up, angled view of a window on a white building.', 'default' ) . '
+
+ + + +
+
' . esc_attr__( 'Close-up of the corner of a white, geometric building with both sharp points and round corners.', 'default' ) . '
+ + + + +
+
+ ', + 'description' => _x( 'Three columns with offset images', 'Block pattern description', 'default' ), +); diff --git a/lib/block-patterns/two-buttons.php b/lib/block-patterns/two-buttons.php new file mode 100644 index 0000000000000..b369fd31ff2ba --- /dev/null +++ b/lib/block-patterns/two-buttons.php @@ -0,0 +1,22 @@ + _x( 'Two buttons', 'Block pattern title', 'default' ), + 'content' => ' + + ', + 'viewportWidth' => 500, + 'categories' => array( 'buttons' ), + 'description' => _x( 'Two buttons, one filled and one outlined, side by side.', 'Block pattern description', 'default' ), +); diff --git a/lib/block-patterns/two-images-gallery.php b/lib/block-patterns/two-images-gallery.php new file mode 100644 index 0000000000000..ec9e413ee3120 --- /dev/null +++ b/lib/block-patterns/two-images-gallery.php @@ -0,0 +1,15 @@ + _x( 'Two images side by side', 'Block pattern title', 'default' ), + 'categories' => array( 'gallery' ), + 'content' => ' + + ', + 'description' => _x( 'Two images side by side', 'Block pattern description', 'default' ), +); diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/adding-patterns.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/adding-patterns.test.js.snap index 85eaf50e88cea..27ddbf03e863d 100644 --- a/packages/e2e-tests/specs/editor/various/__snapshots__/adding-patterns.test.js.snap +++ b/packages/e2e-tests/specs/editor/various/__snapshots__/adding-patterns.test.js.snap @@ -2,12 +2,12 @@ exports[`adding patterns should insert a block pattern 1`] = ` " -
- + " `; From 4986647b7e9bce5ae6d5f4ac37ccf6c6267c4ab3 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Tue, 20 Apr 2021 02:42:11 +0800 Subject: [PATCH 12/20] Enqueue scripts for format library (#30952) --- lib/class-wp-sidebar-block-editor-control.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/class-wp-sidebar-block-editor-control.php b/lib/class-wp-sidebar-block-editor-control.php index cda048ee88191..92830995de266 100644 --- a/lib/class-wp-sidebar-block-editor-control.php +++ b/lib/class-wp-sidebar-block-editor-control.php @@ -24,6 +24,8 @@ class WP_Sidebar_Block_Editor_Control extends WP_Customize_Control { public function enqueue() { wp_enqueue_script( 'wp-customize-widgets' ); wp_enqueue_style( 'wp-customize-widgets' ); + wp_enqueue_script( 'wp-format-library' ); + wp_enqueue_style( 'wp-format-library' ); } /** From ed4aaf80312f5e4105a7623e6d1dd53b7c538391 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Tue, 20 Apr 2021 02:56:37 +0800 Subject: [PATCH 13/20] Fix e2e test failure screenshots not capturing at the right time (#28449) * Fix e2e test failure screenshots not capturing at the right time * Add intentonally failing tests to demo * Move the setup to our own fork of just-environment-puppeteer * Add more comments * Delete test code --- package-lock.json | 277 ++++++++++++++++++ .../e2e-tests/config/setup-debug-artifacts.js | 54 ---- packages/e2e-tests/jest.config.js | 1 - packages/scripts/config/jest-e2e.config.js | 1 + .../jest-environment-puppeteer/index.js | 42 ++- packages/scripts/package.json | 2 + 6 files changed, 321 insertions(+), 56 deletions(-) delete mode 100644 packages/e2e-tests/config/setup-debug-artifacts.js diff --git a/package-lock.json b/package-lock.json index 968f7655a8d27..4de444411b8c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14335,8 +14335,10 @@ "eslint-plugin-markdown": "^1.0.2", "expect-puppeteer": "^4.4.0", "file-loader": "^6.2.0", + "filenamify": "^4.2.0", "ignore-emit-webpack-plugin": "^2.0.6", "jest": "^26.6.3", + "jest-circus": "^26.6.3", "jest-dev-server": "^4.4.0", "jest-environment-node": "^26.6.2", "markdownlint": "^0.18.0", @@ -32124,6 +32126,23 @@ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "optional": true }, + "filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=", + "dev": true + }, + "filenamify": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.2.0.tgz", + "integrity": "sha512-pkgE+4p7N1n7QieOopmn3TqJaefjdWXwEkj2XLZJLKfOgcQKkn11ahvGNgTD8mLggexLiDFQxeTs14xVU22XPA==", + "dev": true, + "requires": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + } + }, "filesize": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz", @@ -36392,6 +36411,246 @@ } } }, + "jest-circus": { + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-26.6.3.tgz", + "integrity": "sha512-ACrpWZGcQMpbv13XbzRzpytEJlilP/Su0JtNCi5r/xLpOUhnaIJr8leYYpLEMgPFURZISEHrnnpmB54Q/UziPw==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^26.6.2", + "@jest/test-result": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/babel__traverse": "^7.0.4", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^26.6.2", + "is-generator-fn": "^2.0.0", + "jest-each": "^26.6.2", + "jest-matcher-utils": "^26.6.2", + "jest-message-util": "^26.6.2", + "jest-runner": "^26.6.3", + "jest-runtime": "^26.6.3", + "jest-snapshot": "^26.6.2", + "jest-util": "^26.6.2", + "pretty-format": "^26.6.2", + "stack-utils": "^2.0.2", + "throat": "^5.0.0" + }, + "dependencies": { + "@jest/console": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", + "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^26.6.2", + "jest-util": "^26.6.2", + "slash": "^3.0.0" + } + }, + "@jest/test-result": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", + "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", + "dev": true, + "requires": { + "@jest/console": "^26.6.2", + "@jest/types": "^26.6.2", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "@types/istanbul-reports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", + "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/stack-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", + "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "jest-message-util": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", + "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/types": "^26.6.2", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "pretty-format": "^26.6.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.2" + } + }, + "jest-util": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", + "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, + "requires": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + } + }, + "react-is": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", + "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + } + }, + "throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, "jest-config": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", @@ -55191,6 +55450,15 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "strong-log-transformer": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz", @@ -57476,6 +57744,15 @@ "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=", "dev": true }, + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "trim-trailing-lines": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.1.tgz", diff --git a/packages/e2e-tests/config/setup-debug-artifacts.js b/packages/e2e-tests/config/setup-debug-artifacts.js deleted file mode 100644 index b061fdd0acbdd..0000000000000 --- a/packages/e2e-tests/config/setup-debug-artifacts.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * External dependencies - */ -const fs = require( 'fs' ); -const util = require( 'util' ); -const root = process.env.GITHUB_WORKSPACE || process.cwd(); -const ARTIFACTS_PATH = root + '/artifacts'; - -const writeFile = util.promisify( fs.writeFile ); - -if ( ! fs.existsSync( ARTIFACTS_PATH ) ) { - fs.mkdirSync( ARTIFACTS_PATH ); -} - -/** - * Gutenberg uses the default jest-jasmine2 test runner that comes with Jest. - * Unfortunately, version 2 of jasmine doesn't support async reporters. It - * does support async before and after hooks though, so the workaround here - * works by making each test wait for the artifacts before starting. - * - * Kudos to Tom Esterez (@testerez) for sharing this idea in https://github.com/smooth-code/jest-puppeteer/issues/131#issuecomment-424073620 - */ -let artifactsPromise; -// eslint-disable-next-line jest/no-jasmine-globals, no-undef -jasmine.getEnv().addReporter( { - specDone: ( result ) => { - if ( result.status === 'failed' ) { - artifactsPromise = storeArtifacts( result.fullName ); - } - }, -} ); - -beforeEach( () => artifactsPromise ); -afterAll( () => artifactsPromise ); - -async function storeArtifacts( testName ) { - const slug = slugify( testName ); - await writeFile( - `${ ARTIFACTS_PATH }/${ slug }-snapshot.html`, - await page.content() - ); - await page.screenshot( { path: `${ ARTIFACTS_PATH }/${ slug }.jpg` } ); -} - -function slugify( testName ) { - const datetime = new Date().toISOString().split( '.' )[ 0 ]; - const readableName = `${ testName } ${ datetime }`; - const slug = readableName - .toLowerCase() - .replace( /:/g, '-' ) - .replace( /[^0-9a-zA-Z \-\(\)]/g, '' ) - .replace( / /g, '-' ); - return slug; -} diff --git a/packages/e2e-tests/jest.config.js b/packages/e2e-tests/jest.config.js index a23def8c2e89d..cbb6eca9358a8 100644 --- a/packages/e2e-tests/jest.config.js +++ b/packages/e2e-tests/jest.config.js @@ -2,7 +2,6 @@ module.exports = { ...require( '@wordpress/scripts/config/jest-e2e.config' ), setupFiles: [ '/config/gutenberg-phase.js' ], setupFilesAfterEnv: [ - '/config/setup-debug-artifacts.js', '/config/setup-test-framework.js', '@wordpress/jest-console', '@wordpress/jest-puppeteer-axe', diff --git a/packages/scripts/config/jest-e2e.config.js b/packages/scripts/config/jest-e2e.config.js index e4dd58c3e4150..05b10123a7b87 100644 --- a/packages/scripts/config/jest-e2e.config.js +++ b/packages/scripts/config/jest-e2e.config.js @@ -9,6 +9,7 @@ const path = require( 'path' ); const { hasBabelConfig } = require( '../utils' ); const jestE2EConfig = { + testRunner: 'jest-circus/runner', globalSetup: path.join( __dirname, 'jest-environment-puppeteer', 'setup' ), globalTeardown: path.join( __dirname, diff --git a/packages/scripts/config/jest-environment-puppeteer/index.js b/packages/scripts/config/jest-environment-puppeteer/index.js index 845a30d1705c7..76f1b4db2ba69 100644 --- a/packages/scripts/config/jest-environment-puppeteer/index.js +++ b/packages/scripts/config/jest-environment-puppeteer/index.js @@ -16,6 +16,9 @@ /** * External dependencies */ +const path = require( 'path' ); +const { writeFile, mkdir } = require( 'fs' ).promises; +const filenamify = require( 'filenamify' ); const NodeEnvironment = require( 'jest-environment-node' ); const chalk = require( 'chalk' ); @@ -25,7 +28,12 @@ const chalk = require( 'chalk' ); const { readConfig, getPuppeteer } = require( './config' ); const handleError = ( error ) => { - process.emit( 'uncaughtException', error ); + // To match the same behavior in jest-jasmine2: + // https://github.com/facebook/jest/blob/1be8d737abd0e2f30e3314184a0efc372ad6d88f/packages/jest-jasmine2/src/jasmine/Env.ts#L250-L251 + // Emitting an uncaughtException event to the process will throw an + // empty error which is very hard to debug in puppeteer context. + // eslint-disable-next-line no-console + console.error( error ); }; const KEYS = { @@ -34,6 +42,9 @@ const KEYS = { ENTER: '\r', }; +const root = process.env.GITHUB_WORKSPACE || process.cwd(); +const ARTIFACTS_PATH = path.join( root, 'artifacts' ); + class PuppeteerEnvironment extends NodeEnvironment { // Jest is not available here, so we have to reverse engineer // the setTimeout function, see https://github.com/facebook/jest/blob/v23.1.0/packages/jest-runtime/src/index.js#L823 @@ -158,6 +169,14 @@ class PuppeteerEnvironment extends NodeEnvironment { }; await this.global.jestPuppeteer.resetBrowser(); + + try { + await mkdir( ARTIFACTS_PATH ); + } catch ( err ) { + if ( err.code !== 'EEXIST' ) { + throw err; + } + } } async teardown() { @@ -179,6 +198,27 @@ class PuppeteerEnvironment extends NodeEnvironment { await browser.disconnect(); } } + + async storeArtifacts( testName ) { + const datetime = new Date().toISOString().split( '.' )[ 0 ]; + const fileName = filenamify( `${ testName } ${ datetime }`, { + replacement: '-', + } ); + await writeFile( + `${ ARTIFACTS_PATH }/${ fileName }-snapshot.html`, + await this.global.page.content() + ); + await this.global.page.screenshot( { + path: `${ ARTIFACTS_PATH }/${ fileName }.jpg`, + } ); + } + + async handleTestEvent( event, state ) { + if ( event.name === 'test_fn_failure' ) { + const testName = state.currentlyRunningTest.name; + await this.storeArtifacts( testName ); + } + } } module.exports = PuppeteerEnvironment; diff --git a/packages/scripts/package.json b/packages/scripts/package.json index c1fe549c78746..604cf6d7c30f2 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -54,8 +54,10 @@ "eslint-plugin-markdown": "^1.0.2", "expect-puppeteer": "^4.4.0", "file-loader": "^6.2.0", + "filenamify": "^4.2.0", "ignore-emit-webpack-plugin": "^2.0.6", "jest": "^26.6.3", + "jest-circus": "^26.6.3", "jest-dev-server": "^4.4.0", "jest-environment-node": "^26.6.2", "markdownlint": "^0.18.0", From 2674d8989221d007302267aa1b5f98ddd31ad064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Justin=20S=C3=A8gb=C3=A9dji=20Ahinon?= Date: Mon, 19 Apr 2021 20:15:59 +0100 Subject: [PATCH 14/20] Fix alignment issue on the nav screen manage locations buttons (#30441) --- .../src/components/inspector-additions/style.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/edit-navigation/src/components/inspector-additions/style.scss b/packages/edit-navigation/src/components/inspector-additions/style.scss index 8feb6f6293d6d..1a63e3bb1bc40 100644 --- a/packages/edit-navigation/src/components/inspector-additions/style.scss +++ b/packages/edit-navigation/src/components/inspector-additions/style.scss @@ -21,6 +21,7 @@ .edit-navigation-manage-locations__menu-entry { display: flex; + align-items: end; margin-bottom: $grid-unit-15; margin-top: $grid-unit-15; .components-custom-select-control, @@ -29,6 +30,7 @@ } button { height: 100%; + margin-bottom: 8px; } } From 19046a4a369249a08949f0bd9d61b2c4995b3bb2 Mon Sep 17 00:00:00 2001 From: sarayourfriend Date: Mon, 19 Apr 2021 12:43:59 -0700 Subject: [PATCH 15/20] block-library: Duplicate media types to avoid bad import (#30973) --- packages/block-library/src/cover/shared.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/cover/shared.js b/packages/block-library/src/cover/shared.js index 18ac6076be922..169e4fe9dc498 100644 --- a/packages/block-library/src/cover/shared.js +++ b/packages/block-library/src/cover/shared.js @@ -4,7 +4,9 @@ import { getBlobTypeByURL, isBlobURL } from '@wordpress/blob'; import { __ } from '@wordpress/i18n'; import { Platform } from '@wordpress/element'; -import { MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO } from '@wordpress/block-editor'; + +const MEDIA_TYPE_IMAGE = 'image'; +const MEDIA_TYPE_VIDEO = 'video'; const POSITION_CLASSNAMES = { 'top left': 'is-position-top-left', From 4ae96fb2867b28a5c8ecb2847e74f99c9904a909 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Mon, 19 Apr 2021 22:12:08 +0100 Subject: [PATCH 16/20] Chore: Cover: Use ALLOWED_MEDIA_TYPES shared constant. (#30977) --- packages/block-library/src/cover/edit.js | 3 ++- packages/block-library/src/cover/shared.js | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index ec167b51ce301..e5ea8c26fc9df 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -49,6 +49,7 @@ import { isBlobURL } from '@wordpress/blob'; * Internal dependencies */ import { + ALLOWED_MEDIA_TYPES, attributesFromMedia, IMAGE_BACKGROUND_TYPE, VIDEO_BACKGROUND_TYPE, @@ -63,7 +64,7 @@ import { /** * Module Constants */ -const ALLOWED_MEDIA_TYPES = [ 'image', 'video' ]; + const INNER_BLOCKS_TEMPLATE = [ [ 'core/paragraph', diff --git a/packages/block-library/src/cover/shared.js b/packages/block-library/src/cover/shared.js index 169e4fe9dc498..2ab602be5467c 100644 --- a/packages/block-library/src/cover/shared.js +++ b/packages/block-library/src/cover/shared.js @@ -5,9 +5,6 @@ import { getBlobTypeByURL, isBlobURL } from '@wordpress/blob'; import { __ } from '@wordpress/i18n'; import { Platform } from '@wordpress/element'; -const MEDIA_TYPE_IMAGE = 'image'; -const MEDIA_TYPE_VIDEO = 'video'; - const POSITION_CLASSNAMES = { 'top left': 'is-position-top-left', 'top center': 'is-position-top-center', @@ -29,7 +26,7 @@ export const COVER_DEFAULT_HEIGHT = 300; export function backgroundImageStyles( url ) { return url ? { backgroundImage: `url(${ url })` } : {}; } -export const ALLOWED_MEDIA_TYPES = [ MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO ]; +export const ALLOWED_MEDIA_TYPES = [ 'image', 'video' ]; const isWeb = Platform.OS === 'web'; From c9e22a8c9db31ee02dfd5029eea446862737717d Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Tue, 20 Apr 2021 12:20:56 +1000 Subject: [PATCH 17/20] Cover block: Rename isBlogUrl to isUploadingMedia (#30435) * Cover block: Rename isBlogUrl to isTemporaryImage * Add isTemporaryMedia function, rename constant to isUploadingMedia Co-authored-by: Andrew Serong --- packages/block-library/src/cover/edit.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index e5ea8c26fc9df..f7c75b5737675 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -241,6 +241,17 @@ function mediaPosition( { x, y } ) { return `${ Math.round( x * 100 ) }% ${ Math.round( y * 100 ) }%`; } +/** + * Is the URL a temporary blob URL? A blob URL is one that is used temporarily while + * the media (image or video) is being uploaded and will not have an id allocated yet. + * + * @param {number} id The id of the media. + * @param {string} url The url of the media. + * + * @return {boolean} Is the URL a Blob URL. + */ +const isTemporaryMedia = ( id, url ) => ! id && isBlobURL( url ); + function CoverPlaceholder( { hasBackground = false, children, @@ -302,7 +313,7 @@ function CoverEdit( { setGradient, } = __experimentalUseGradient(); const onSelectMedia = attributesFromMedia( setAttributes ); - const isBlogUrl = isBlobURL( url ); + const isUploadingMedia = isTemporaryMedia( id, url ); const [ prevMinHeightValue, setPrevMinHeightValue ] = useState( minHeight ); const [ prevMinHeightUnit, setPrevMinHeightUnit ] = useState( @@ -575,7 +586,7 @@ function CoverEdit( { { 'is-dark-theme': isDark, 'has-background-dim': dimRatio !== 0, - 'is-transient': isBlogUrl, + 'is-transient': isUploadingMedia, 'has-parallax': hasParallax, 'is-repeated': isRepeated, [ overlayColor.class ]: overlayColor.class, @@ -645,7 +656,7 @@ function CoverEdit( { style={ mediaStyle } /> ) } - { isBlogUrl && } + { isUploadingMedia && } Date: Tue, 20 Apr 2021 10:31:38 +0800 Subject: [PATCH 18/20] Add media uploader capabilities to block-based widget customize screen (#30954) * Implement media uploading * Use gutenberg_initialize_editor to load the editor and pass through block settings * Avoid multiple block registration * Update to use new get default block editor settings function --- lib/class-wp-sidebar-block-editor-control.php | 10 ----- lib/widgets-customize.php | 29 ++++++++++++++ package-lock.json | 2 + packages/customize-widgets/package.json | 2 + .../components/sidebar-block-editor/index.js | 39 ++++++++++++++++--- .../src/controls/sidebar-control.js | 3 +- .../customize-widgets/src/filters/index.js | 1 + .../src/filters/replace-media-upload.js | 13 +++++++ packages/customize-widgets/src/index.js | 11 ++++-- 9 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 packages/customize-widgets/src/filters/replace-media-upload.js diff --git a/lib/class-wp-sidebar-block-editor-control.php b/lib/class-wp-sidebar-block-editor-control.php index 92830995de266..9773f807fc316 100644 --- a/lib/class-wp-sidebar-block-editor-control.php +++ b/lib/class-wp-sidebar-block-editor-control.php @@ -18,16 +18,6 @@ class WP_Sidebar_Block_Editor_Control extends WP_Customize_Control { */ public $type = 'sidebar_block_editor'; - /** - * Enqueue the scripts and styles. - */ - public function enqueue() { - wp_enqueue_script( 'wp-customize-widgets' ); - wp_enqueue_style( 'wp-customize-widgets' ); - wp_enqueue_script( 'wp-format-library' ); - wp_enqueue_style( 'wp-format-library' ); - } - /** * Render the widgets block editor container. */ diff --git a/lib/widgets-customize.php b/lib/widgets-customize.php index e0793ff383bf1..df1c1cd9f5c31 100644 --- a/lib/widgets-customize.php +++ b/lib/widgets-customize.php @@ -106,7 +106,36 @@ function gutenberg_widgets_customize_add_unstable_instance( $args, $id ) { return $args; } +/** + * Initialize the Gutenberg customize widgets page. + */ +function gutenberg_customize_widgets_init() { + $settings = array_merge( + gutenberg_get_default_block_editor_settings(), + gutenberg_get_legacy_widget_settings() + ); + + // This purposefully does not rely on apply_filters( 'block_editor_settings', $settings, null ); + // Applying that filter would bring over multitude of features from the post editor, some of which + // may be undesirable. Instead of using that filter, we simply pick just the settings that are needed. + $settings = gutenberg_experimental_global_styles_settings( $settings ); + $settings = gutenberg_extend_block_editor_styles( $settings ); + + gutenberg_initialize_editor( + 'widgets_customizer', + 'customize-widgets', + array( + 'editor_settings' => $settings, + ) + ); + wp_enqueue_script( 'wp-customize-widgets' ); + wp_enqueue_style( 'wp-customize-widgets' ); + wp_enqueue_script( 'wp-format-library' ); + wp_enqueue_style( 'wp-format-library' ); +} + if ( gutenberg_is_experiment_enabled( 'gutenberg-widgets-in-customizer' ) ) { add_action( 'customize_register', 'gutenberg_widgets_customize_register' ); add_filter( 'widget_customizer_setting_args', 'gutenberg_widgets_customize_add_unstable_instance', 10, 2 ); + add_action( 'customize_controls_enqueue_scripts', 'gutenberg_customize_widgets_init' ); } diff --git a/package-lock.json b/package-lock.json index 4de444411b8c2..eac2293504741 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13485,6 +13485,7 @@ "@wordpress/blocks": "file:packages/blocks", "@wordpress/components": "file:packages/components", "@wordpress/compose": "file:packages/compose", + "@wordpress/core-data": "file:packages/core-data", "@wordpress/data": "file:packages/data", "@wordpress/dom": "file:packages/dom", "@wordpress/element": "file:packages/element", @@ -13493,6 +13494,7 @@ "@wordpress/icons": "file:packages/icons", "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", "@wordpress/keycodes": "file:packages/keycodes", + "@wordpress/media-utils": "file:packages/media-utils", "classnames": "^2.2.6", "lodash": "^4.17.19" }, diff --git a/packages/customize-widgets/package.json b/packages/customize-widgets/package.json index 8a4dc046cea26..673d09b927424 100644 --- a/packages/customize-widgets/package.json +++ b/packages/customize-widgets/package.json @@ -28,6 +28,7 @@ "@wordpress/blocks": "file:../blocks", "@wordpress/components": "file:../components", "@wordpress/compose": "file:../compose", + "@wordpress/core-data": "file:../core-data", "@wordpress/data": "file:../data", "@wordpress/dom": "file:../dom", "@wordpress/element": "file:../element", @@ -36,6 +37,7 @@ "@wordpress/icons": "file:../icons", "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/keycodes": "file:../keycodes", + "@wordpress/media-utils": "file:../media-utils", "classnames": "^2.2.6", "lodash": "^4.17.19" }, diff --git a/packages/customize-widgets/src/components/sidebar-block-editor/index.js b/packages/customize-widgets/src/components/sidebar-block-editor/index.js index a5befcf9cb5a3..61b4428a7d374 100644 --- a/packages/customize-widgets/src/components/sidebar-block-editor/index.js +++ b/packages/customize-widgets/src/components/sidebar-block-editor/index.js @@ -1,6 +1,13 @@ +/** + * External dependencies + */ +import { defaultTo } from 'lodash'; + /** * WordPress dependencies */ +import { store as coreStore } from '@wordpress/core-data'; +import { useSelect } from '@wordpress/data'; import { useMemo, createPortal } from '@wordpress/element'; import { BlockList, @@ -12,6 +19,7 @@ import { __experimentalBlockSettingsMenuFirstItem, } from '@wordpress/block-editor'; import { SlotFillProvider, Popover } from '@wordpress/components'; +import { uploadMedia } from '@wordpress/media-utils'; /** * Internal dependencies @@ -21,14 +29,35 @@ import Header from '../header'; import useInserter from '../inserter/use-inserter'; import SidebarEditorProvider from './sidebar-editor-provider'; -export default function SidebarBlockEditor( { sidebar, inserter, inspector } ) { +export default function SidebarBlockEditor( { + blockEditorSettings, + sidebar, + inserter, + inspector, +} ) { const [ isInserterOpened, setIsInserterOpened ] = useInserter( inserter ); - const settings = useMemo( - () => ( { - __experimentalSetIsInserterOpened: setIsInserterOpened, - } ), + const hasUploadPermissions = useSelect( + ( select ) => + defaultTo( select( coreStore ).canUser( 'create', 'media' ), true ), [] ); + const settings = useMemo( () => { + let mediaUploadBlockEditor; + if ( hasUploadPermissions ) { + mediaUploadBlockEditor = ( { onError, ...argumentsObject } ) => { + uploadMedia( { + wpAllowedMimeTypes: blockEditorSettings.allowedMimeTypes, + onError: ( { message } ) => onError( message ), + ...argumentsObject, + } ); + }; + } + + return { + __experimentalSetIsInserterOpened: setIsInserterOpened, + mediaUpload: mediaUploadBlockEditor, + }; + }, [] ); const parentContainer = document.getElementById( 'customize-theme-controls' ); diff --git a/packages/customize-widgets/src/controls/sidebar-control.js b/packages/customize-widgets/src/controls/sidebar-control.js index 02e6a8f154d79..4fbbb7f8f2aea 100644 --- a/packages/customize-widgets/src/controls/sidebar-control.js +++ b/packages/customize-widgets/src/controls/sidebar-control.js @@ -12,7 +12,7 @@ import getInserterOuterSection from './inserter-outer-section'; const getInserterId = ( controlId ) => `widgets-inserter-${ controlId }`; -export default function getSidebarControl() { +export default function getSidebarControl( blockEditorSettings ) { const { wp: { customize }, } = window; @@ -46,6 +46,7 @@ export default function getSidebarControl() { if ( this.sectionInstance.expanded() ) { render( MediaUpload; + +addFilter( + 'editor.MediaUpload', + 'core/edit-widgets/replace-media-upload', + replaceMediaUpload +); diff --git a/packages/customize-widgets/src/index.js b/packages/customize-widgets/src/index.js index 627b26b6eeeda..985ba6b4383e4 100644 --- a/packages/customize-widgets/src/index.js +++ b/packages/customize-widgets/src/index.js @@ -18,8 +18,11 @@ const { wp } = window; /** * Initializes the widgets block editor in the customizer. + * + * @param {string} editorName The editor name. + * @param {Object} blockEditorSettings Block editor settings. */ -export function initialize() { +export function initialize( editorName, blockEditorSettings ) { const coreBlocks = __experimentalGetCoreBlocks().filter( ( block ) => ! [ 'core/more' ].includes( block.name ) ); @@ -32,7 +35,7 @@ export function initialize() { } wp.customize.sectionConstructor.sidebar = getSidebarSection(); - wp.customize.controlConstructor.sidebar_block_editor = getSidebarControl(); + wp.customize.controlConstructor.sidebar_block_editor = getSidebarControl( + blockEditorSettings + ); } - -wp.domReady( initialize ); From 58d93d8947141051f04588517c4292316e1b34e1 Mon Sep 17 00:00:00 2001 From: amarinediary Date: Tue, 20 Apr 2021 04:54:00 +0200 Subject: [PATCH 19/20] Documentation: Improve semantics in the block-based theme guide (#30946) * Semantic update Changed "theme name" to "theme text domain". "Theme name" suggest the actual theme name with spaces and caps as defined in the style.css file example on line 66: `Theme Name: My first theme`. In the given example, the "theme text domain" is actually used. Therefore to avoid any confusion it should be changed to "theme text domain", as per line 78: `Text Domain: myfirsttheme`. * [Erratum #30946] [Erratum #30946] --- docs/how-to-guides/block-based-theme/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/how-to-guides/block-based-theme/README.md b/docs/how-to-guides/block-based-theme/README.md index ae63552573428..d39dd3b0caaa4 100644 --- a/docs/how-to-guides/block-based-theme/README.md +++ b/docs/how-to-guides/block-based-theme/README.md @@ -172,7 +172,7 @@ Inside the block-templates folder, create an `index.html` file. In `index.html`, include the template parts by adding two HTML comments. -The HTML comments starts with `wp:template-part` which is the name of the template-part block type. Inside the curly brackets are two keys and their values: The slug of the template part, and the theme name. +The HTML comments starts with `wp:template-part` which is the name of the template-part block type. Inside the curly brackets are two keys and their values: The slug of the template part, and the theme text domain. ``` @@ -180,7 +180,7 @@ The HTML comments starts with `wp:template-part` which is the name of the templa ``` -If you used a different theme name, adjust the value for the theme key. +If you used a different theme name, adjust the value for the theme text domain. Eventually, you will be able to create and combine templates and template parts directly in the site editor. From c164be6fc0afd5baa891cc216451164fcabc3881 Mon Sep 17 00:00:00 2001 From: Sabrina Zeidan Date: Tue, 20 Apr 2021 09:34:56 +0300 Subject: [PATCH 20/20] Add placeholder attribute to the list block (#30958) in block template --- packages/block-library/src/list/block.json | 3 +++ packages/block-library/src/list/edit.js | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/list/block.json b/packages/block-library/src/list/block.json index 509da21bef437..7af618fb75388 100644 --- a/packages/block-library/src/list/block.json +++ b/packages/block-library/src/list/block.json @@ -25,6 +25,9 @@ }, "reversed": { "type": "boolean" + }, + "placeholder": { + "type": "string" } }, "supports": { diff --git a/packages/block-library/src/list/edit.js b/packages/block-library/src/list/edit.js index 8a9c8af53dd65..9a98fd282d4c6 100644 --- a/packages/block-library/src/list/edit.js +++ b/packages/block-library/src/list/edit.js @@ -43,7 +43,7 @@ export default function ListEdit( { onReplace, isSelected, } ) { - const { ordered, values, type, reversed, start } = attributes; + const { ordered, values, type, reversed, start, placeholder } = attributes; const tagName = ordered ? 'ol' : 'ul'; const controls = ( { value, onChange, onFocus } ) => ( @@ -155,7 +155,7 @@ export default function ListEdit( { } value={ values } aria-label={ __( 'List text' ) } - placeholder={ __( 'List' ) } + placeholder={ placeholder || __( 'List' ) } onMerge={ mergeBlocks } onSplit={ ( value ) => createBlock( name, { ...attributes, values: value } ) @@ -178,6 +178,7 @@ export default function ListEdit( { ordered={ ordered } reversed={ reversed } start={ start } + placeholder={ placeholder } /> ) }