diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index e9a0e0b85bdc7..becb96ca60b1c 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -144,6 +144,7 @@ const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => { getSelectedBlockCount, getBlockName, __unstableGetContentLockingParent, + getTemplateLock, } = select( blockEditorStore ); const _selectedBlockClientId = getSelectedBlockClientId(); @@ -157,9 +158,11 @@ const BlockInspector = ( { showNoBlockSelectedMessage = true } ) => { selectedBlockClientId: _selectedBlockClientId, selectedBlockName: _selectedBlockName, blockType: _blockType, - topLevelLockedBlock: __unstableGetContentLockingParent( - _selectedBlockClientId - ), + topLevelLockedBlock: + __unstableGetContentLockingParent( _selectedBlockClientId ) || + ( getTemplateLock( _selectedBlockClientId ) === 'contentOnly' + ? _selectedBlockClientId + : undefined ), }; }, [] ); diff --git a/packages/block-editor/src/components/block-patterns-list/style.scss b/packages/block-editor/src/components/block-patterns-list/style.scss index 8ae80f63ce1e3..3b5c2571be0fa 100644 --- a/packages/block-editor/src/components/block-patterns-list/style.scss +++ b/packages/block-editor/src/components/block-patterns-list/style.scss @@ -2,6 +2,11 @@ cursor: pointer; margin-bottom: $grid-unit-30; + // The list item contains absolutely positioned visually hidden text, + // so make this container relative. This prevents the bug experienced in + // https://github.com/WordPress/gutenberg/issues/44842. + position: relative; + &.is-placeholder { min-height: 100px; } diff --git a/packages/block-editor/src/components/button-block-appender/style.scss b/packages/block-editor/src/components/button-block-appender/style.scss index 45358b9a0f050..b2af07e50a944 100644 --- a/packages/block-editor/src/components/button-block-appender/style.scss +++ b/packages/block-editor/src/components/button-block-appender/style.scss @@ -36,8 +36,9 @@ .block-list-appender:only-child { .is-layout-constrained.block-editor-block-list__block:not(.is-selected) > &, .is-layout-flow.block-editor-block-list__block:not(.is-selected) > &, - .is-layout-constrained.block-editor-block-list__block:not(.is-selected) > .block-editor-block-list__layout > &, - .is-layout-flow.block-editor-block-list__block:not(.is-selected) > .block-editor-block-list__layout > & { + // Legacy groups have an inner container so need to be targeted separately + .is-layout-constrained.block-editor-block-list__block:not(.is-selected) > .wp-block-group__inner-container > &, + .is-layout-flow.block-editor-block-list__block:not(.is-selected) > .wp-block-group__inner-container > & { pointer-events: none; &::after { diff --git a/packages/block-editor/src/components/inner-blocks/use-inner-block-template-sync.js b/packages/block-editor/src/components/inner-blocks/use-inner-block-template-sync.js index 5eff89609f6d8..1b81069c44f3d 100644 --- a/packages/block-editor/src/components/inner-blocks/use-inner-block-template-sync.js +++ b/packages/block-editor/src/components/inner-blocks/use-inner-block-template-sync.js @@ -40,7 +40,7 @@ export default function useInnerBlockTemplateSync( templateLock, templateInsertUpdatesSelection ) { - const { getSelectedBlocksInitialCaretPosition } = + const { getSelectedBlocksInitialCaretPosition, isBlockSelected } = useSelect( blockEditorStore ); const { replaceInnerBlocks } = useDispatch( blockEditorStore ); const innerBlocks = useSelect( @@ -86,7 +86,8 @@ export default function useInnerBlockTemplateSync( nextBlocks, currentInnerBlocks.length === 0 && templateInsertUpdatesSelection && - nextBlocks.length !== 0, + nextBlocks.length !== 0 && + isBlockSelected( clientId ), // This ensures the "initialPosition" doesn't change when applying the template // If we're supposed to focus the block, we'll focus the first inner block // otherwise, we won't apply any auto-focus. diff --git a/packages/block-editor/src/components/spacing-sizes-control/spacing-input-control.js b/packages/block-editor/src/components/spacing-sizes-control/spacing-input-control.js index cf9a663fe1fa1..4a08bb9b3348a 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/spacing-input-control.js +++ b/packages/block-editor/src/components/spacing-sizes-control/spacing-input-control.js @@ -221,6 +221,7 @@ export default function SpacingInputControl( { hideLabelFromVision={ true } className="components-spacing-sizes-control__custom-value-input" style={ { gridColumn: '1' } } + size={ '__unstable-large' } /> ) } diff --git a/packages/block-editor/src/components/spacing-sizes-control/style.scss b/packages/block-editor/src/components/spacing-sizes-control/style.scss index 27b83a9ba5ca8..02731920c5e6f 100644 --- a/packages/block-editor/src/components/spacing-sizes-control/style.scss +++ b/packages/block-editor/src/components/spacing-sizes-control/style.scss @@ -2,7 +2,7 @@ display: grid; grid-template-columns: auto 1fr auto; align-items: center; - grid-template-rows: 25px auto; + grid-template-rows: 16px auto; } .component-spacing-sizes-control { @@ -27,7 +27,7 @@ grid-column: 1 / 1; justify-content: left; height: $grid-unit-20; - margin-top: $grid-unit-15; + margin-top: $grid-unit-20; } .components-spacing-sizes-control__side-label { @@ -37,8 +37,9 @@ } &.is-unlinked { - .components-range-control.components-spacing-sizes-control__range-control { - margin-top: $grid-unit-15; + .components-range-control.components-spacing-sizes-control__range-control, + .components-spacing-sizes-control__custom-value-input { + margin-top: $grid-unit-10; } } @@ -60,12 +61,7 @@ grid-column: 2 / 2; grid-row: 1 / 1; justify-self: end; - padding: 0; - &.is-small.has-icon { - padding: 0; - min-width: $icon-size; - height: $grid-unit-20; - } + margin-top: -4px; } .component-spacing-sizes-control__linked-button ~ .components-spacing-sizes-control__custom-toggle-all { @@ -75,33 +71,43 @@ .components-spacing-sizes-control__custom-toggle-single { grid-column: 3 / 3; justify-self: end; - &.is-small.has-icon { - padding: 0; - min-width: $icon-size; - height: $grid-unit-20; - margin-top: $grid-unit-15; - } + margin-top: $grid-unit-15; } .component-spacing-sizes-control__linked-button { grid-column: 3 / 3; grid-row: 1 / 1; justify-self: end; + line-height: 0; + margin-top: -4px; } .components-spacing-sizes-control__custom-value-range { grid-column: span 2; - margin-left: $grid-unit-10; - height: 30px; + margin-left: $grid-unit-20; + margin-top: 8px; } .components-spacing-sizes-control__custom-value-input { width: 124px; + margin-top: 8px; + } + + .components-range-control { + height: 40px; + /* Vertically center the RangeControl until it has true 40px height. */ + display: flex; + align-items: center; + + > .components-base-control__field { + /* Fixes RangeControl contents when the outer wrapper is flex */ + flex: 1; + } } .components-spacing-sizes-control__range-control { grid-column: span 3; - height: 40px; + margin-top: 8px; } .components-range-control__mark { @@ -125,5 +131,6 @@ .components-spacing-sizes-control__custom-select-control { grid-column: span 3; + margin-top: $grid-unit-10; } } diff --git a/packages/block-library/src/avatar/index.php b/packages/block-library/src/avatar/index.php index 9e20d81b648ed..f6e3f6a7eeaf2 100644 --- a/packages/block-library/src/avatar/index.php +++ b/packages/block-library/src/avatar/index.php @@ -127,7 +127,7 @@ function render_block_core_avatar( $attributes, $content, $block ) { $label = 'aria-label="' . sprintf( esc_attr__( '(%s website link, opens in a new tab)' ), $comment->comment_author ) . '"'; } // translators: %1$s: Comment Author website link. %2$s: Link target. %3$s Aria label. %4$s Avatar image. - $avatar_block = sprintf( '%4$s', $comment->comment_author_url, esc_attr( $attributes['linkTarget'] ), $label, $avatar_block ); + $avatar_block = sprintf( '%4$s', esc_url( $comment->comment_author_url ), esc_attr( $attributes['linkTarget'] ), $label, $avatar_block ); } return sprintf( '
%2s
', $wrapper_attributes, $avatar_block ); } diff --git a/packages/block-library/src/list-item/hooks/use-outdent-list-item.js b/packages/block-library/src/list-item/hooks/use-outdent-list-item.js index 93472f49dbba8..d4c034b06e813 100644 --- a/packages/block-library/src/list-item/hooks/use-outdent-list-item.js +++ b/packages/block-library/src/list-item/hooks/use-outdent-list-item.js @@ -20,12 +20,16 @@ export default function useOutdentListItem( clientId ) { const registry = useRegistry(); const { canOutdent } = useSelect( ( innerSelect ) => { - const { getBlockRootClientId } = innerSelect( blockEditorStore ); + const { getBlockRootClientId, getBlockName } = + innerSelect( blockEditorStore ); const grandParentId = getBlockRootClientId( getBlockRootClientId( clientId ) ); + const grandParentName = getBlockName( grandParentId ); + const isListItem = grandParentName === listItemName; + return { - canOutdent: !! grandParentId, + canOutdent: isListItem, }; }, [ clientId ] diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index 33e1ebdbab56d..c3f6317eb2955 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -646,7 +646,7 @@ function render_block_core_navigation( $attributes, $content, $block ) { $toggle_aria_label_close, esc_attr( implode( ' ', $responsive_container_classes ) ), esc_attr( implode( ' ', $open_button_classes ) ), - safecss_filter_attr( $colors['overlay_inline_styles'] ), + esc_attr( safecss_filter_attr( $colors['overlay_inline_styles'] ) ), __( 'Menu' ), $toggle_button_content, $toggle_close_button_content diff --git a/packages/block-library/src/rss/index.php b/packages/block-library/src/rss/index.php index 0edbc90490c31..e32155195af1d 100644 --- a/packages/block-library/src/rss/index.php +++ b/packages/block-library/src/rss/index.php @@ -20,7 +20,7 @@ function render_block_core_rss( $attributes ) { $rss = fetch_feed( $attributes['feedURL'] ); if ( is_wp_error( $rss ) ) { - return '
' . __( 'RSS Error:' ) . ' ' . $rss->get_error_message() . '
'; + return '
' . __( 'RSS Error:' ) . ' ' . esc_html( $rss->get_error_message() ) . '
'; } if ( ! $rss->get_item_quantity() ) { @@ -48,8 +48,8 @@ function render_block_core_rss( $attributes ) { if ( $date ) { $date = sprintf( ' ', - date_i18n( get_option( 'c' ), $date ), - date_i18n( get_option( 'date_format' ), $date ) + esc_attr( date_i18n( get_option( 'c' ), $date ) ), + esc_attr( date_i18n( get_option( 'date_format' ), $date ) ) ); } } diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php index 61b2cf2b06b51..26b6a7585ccc7 100644 --- a/packages/block-library/src/search/index.php +++ b/packages/block-library/src/search/index.php @@ -367,12 +367,12 @@ function styles_for_block_core_search( $attributes ) { // Add color styles. $has_text_color = ! empty( $attributes['style']['color']['text'] ); if ( $has_text_color ) { - $button_styles[] = sprintf( 'color: %s;', esc_attr( $attributes['style']['color']['text'] ) ); + $button_styles[] = sprintf( 'color: %s;', $attributes['style']['color']['text'] ); } $has_background_color = ! empty( $attributes['style']['color']['background'] ); if ( $has_background_color ) { - $button_styles[] = sprintf( 'background-color: %s;', esc_attr( $attributes['style']['color']['background'] ) ); + $button_styles[] = sprintf( 'background-color: %s;', $attributes['style']['color']['background'] ); } $has_custom_gradient = ! empty( $attributes['style']['color']['gradient'] ); @@ -399,9 +399,9 @@ function styles_for_block_core_search( $attributes ) { } return array( - 'input' => ! empty( $input_styles ) ? sprintf( ' style="%s"', safecss_filter_attr( implode( ' ', $input_styles ) ) ) : '', - 'button' => ! empty( $button_styles ) ? sprintf( ' style="%s"', safecss_filter_attr( implode( ' ', $button_styles ) ) ) : '', - 'wrapper' => ! empty( $wrapper_styles ) ? sprintf( ' style="%s"', safecss_filter_attr( implode( ' ', $wrapper_styles ) ) ) : '', + 'input' => ! empty( $input_styles ) ? sprintf( ' style="%s"', esc_attr( safecss_filter_attr( implode( ' ', $input_styles ) ) ) ) : '', + 'button' => ! empty( $button_styles ) ? sprintf( ' style="%s"', esc_attr( safecss_filter_attr( implode( ' ', $button_styles ) ) ) ) : '', + 'wrapper' => ! empty( $wrapper_styles ) ? sprintf( ' style="%s"', esc_attr( safecss_filter_attr( implode( ' ', $wrapper_styles ) ) ) ) : '', 'label' => ! empty( $label_styles ) ? sprintf( ' style="%s"', esc_attr( safecss_filter_attr( implode( ' ', $label_styles ) ) ) ) : '', ); } diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 31ad5b0457d7e..bf32aadd73ff5 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -5,6 +5,7 @@ ### Bug Fix - `FontSizePicker`: Ensure that fluid font size presets appear correctly in the UI controls ([#44791](https://github.com/WordPress/gutenberg/pull/44791)) +- `Navigator`: restore focus only once per location ([#44972](https://github.com/WordPress/gutenberg/pull/44972)). - The `LinkedButton` to unlink sides in `BoxControl`, `BorderBoxControl` and `BorderRadiusControl` have changed from a rectangular primary button to an icon-only button, with a sentence case tooltip, and default-size icon for better legibility. The `Button` component has been fixed so when `isSmall` and `icon` props are set, and no text is present, the button shape is square rather than rectangular. - `Popover`: fix limitShift logic by adding iframe offset correctly [#42950](https://github.com/WordPress/gutenberg/pull/42950)). diff --git a/packages/components/src/font-size-picker/index.js b/packages/components/src/font-size-picker/index.js index 1fb266bba02b2..47668eb2dca16 100644 --- a/packages/components/src/font-size-picker/index.js +++ b/packages/components/src/font-size-picker/index.js @@ -116,7 +116,7 @@ function FontSizePicker( } // Calculate the `hint` for toggle group control. - let hint = selectedOption.name; + let hint = selectedOption?.name || selectedOption.slug; if ( ! fontSizesContainComplexValues && typeof selectedOption.size === 'string' diff --git a/packages/components/src/navigator/navigator-provider/component.tsx b/packages/components/src/navigator/navigator-provider/component.tsx index 634c6f8204b10..fc769f2f5736c 100644 --- a/packages/components/src/navigator/navigator-provider/component.tsx +++ b/packages/components/src/navigator/navigator-provider/component.tsx @@ -49,6 +49,7 @@ function NavigatorProvider( ...options, path, isBack: false, + hasRestoredFocus: false, }, ] ); }, @@ -62,6 +63,7 @@ function NavigatorProvider( { ...locationHistory[ locationHistory.length - 2 ], isBack: true, + hasRestoredFocus: false, }, ] ); } diff --git a/packages/components/src/navigator/navigator-screen/component.tsx b/packages/components/src/navigator/navigator-screen/component.tsx index 94b7bfee306e9..092b95b50d97f 100644 --- a/packages/components/src/navigator/navigator-screen/component.tsx +++ b/packages/components/src/navigator/navigator-screen/component.tsx @@ -79,7 +79,13 @@ function NavigatorScreen( props: Props, forwardedRef: ForwardedRef< any > ) { // - if the current location is not the initial one (to avoid moving focus on page load) // - when the screen becomes visible // - if the wrapper ref has been assigned - if ( isInitialLocation || ! isMatch || ! wrapperRef.current ) { + // - if focus hasn't already been restored for the current location + if ( + isInitialLocation || + ! isMatch || + ! wrapperRef.current || + location.hasRestoredFocus + ) { return; } @@ -103,10 +109,12 @@ function NavigatorScreen( props: Props, forwardedRef: ForwardedRef< any > ) { elementToFocus = firstTabbable ?? wrapperRef.current; } + location.hasRestoredFocus = true; elementToFocus.focus(); }, [ isInitialLocation, isMatch, + location.hasRestoredFocus, location.isBack, previousLocation?.focusTargetSelector, ] ); diff --git a/packages/components/src/navigator/types.ts b/packages/components/src/navigator/types.ts index edc80c356e18e..217b940892337 100644 --- a/packages/components/src/navigator/types.ts +++ b/packages/components/src/navigator/types.ts @@ -11,6 +11,7 @@ export type NavigatorLocation = NavigateOptions & { isInitial?: boolean; isBack?: boolean; path?: string; + hasRestoredFocus?: boolean; }; export type NavigatorContext = { diff --git a/packages/env/lib/download-wp-phpunit.js b/packages/env/lib/download-wp-phpunit.js index 923750d58b1d7..3900fd9495343 100644 --- a/packages/env/lib/download-wp-phpunit.js +++ b/packages/env/lib/download-wp-phpunit.js @@ -122,7 +122,7 @@ async function downloadTestSuite( // Alpha, Beta, and RC versions are bleeding edge and should pull from trunk. let ref; const fetchRaw = []; - if ( ! wpVersion || wpVersion.match( /-(?:alpha|beta|rc)/ ) ) { + if ( ! wpVersion || wpVersion.match( /-(?:alpha|beta|rc)/i ) ) { ref = 'trunk'; fetchRaw.push( 'fetch', 'origin', ref, '--depth', '1' ); } else { diff --git a/packages/widgets/src/blocks/legacy-widget/index.php b/packages/widgets/src/blocks/legacy-widget/index.php index c3aa55fdfe241..94cfb9ba71ebf 100644 --- a/packages/widgets/src/blocks/legacy-widget/index.php +++ b/packages/widgets/src/blocks/legacy-widget/index.php @@ -34,7 +34,7 @@ function render_block_core_legacy_widget( $attributes ) { if ( isset( $attributes['instance']['encoded'], $attributes['instance']['hash'] ) ) { $serialized_instance = base64_decode( $attributes['instance']['encoded'] ); - if ( wp_hash( $serialized_instance ) !== $attributes['instance']['hash'] ) { + if ( ! hash_equals( wp_hash( $serialized_instance ), (string) $attributes['instance']['hash'] ) ) { return ''; } $instance = unserialize( $serialized_instance ); diff --git a/packages/widgets/src/blocks/widget-group/index.php b/packages/widgets/src/blocks/widget-group/index.php index 6cf6442346a30..8c8584b296d57 100644 --- a/packages/widgets/src/blocks/widget-group/index.php +++ b/packages/widgets/src/blocks/widget-group/index.php @@ -28,7 +28,7 @@ function render_block_core_widget_group( $attributes, $content, $block ) { $html = ''; if ( ! empty( $attributes['title'] ) ) { - $html .= $before_title . $attributes['title'] . $after_title; + $html .= $before_title . esc_html( $attributes['title'] ) . $after_title; } $html .= '
'; diff --git a/test/e2e/specs/editor/blocks/__snapshots__/List-can-be-exited-to-selected-paragraph-1-chromium.txt b/test/e2e/specs/editor/blocks/__snapshots__/List-can-be-exited-to-selected-paragraph-1-chromium.txt new file mode 100644 index 0000000000000..468d73c851444 --- /dev/null +++ b/test/e2e/specs/editor/blocks/__snapshots__/List-can-be-exited-to-selected-paragraph-1-chromium.txt @@ -0,0 +1,9 @@ + + + + + +

1

+ \ No newline at end of file diff --git a/test/e2e/specs/editor/blocks/list.spec.js b/test/e2e/specs/editor/blocks/list.spec.js index 76c334257387d..ad63edc6fbaf0 100644 --- a/test/e2e/specs/editor/blocks/list.spec.js +++ b/test/e2e/specs/editor/blocks/list.spec.js @@ -602,6 +602,32 @@ test.describe( 'List', () => { ); } ); + test( 'should create paragraph on Enter in quote block', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { name: 'core/quote' } ); + await page.keyboard.type( '/list' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'aaa' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.press( 'Enter' ); + + await expect.poll( editor.getEditedPostContent ).toBe( + ` +
+
    +
  • aaa
  • +
+ + + +

+
+` + ); + } ); + test( 'should indent and outdent level 1', async ( { editor, page } ) => { await editor.insertBlock( { name: 'core/list' } ); await page.keyboard.type( 'a' ); @@ -1199,4 +1225,13 @@ test.describe( 'List', () => { ` ); } ); + + test( 'can be exited to selected paragraph', async ( { editor, page } ) => { + await page.click( 'role=button[name="Add default block"i]' ); + await page.keyboard.type( '* ' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '1' ); + + expect( await editor.getEditedPostContent() ).toMatchSnapshot(); + } ); } );