diff --git a/package-lock.json b/package-lock.json index 172310d695ecd2..462734073db274 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5124,32 +5124,42 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.4.1.tgz", - "integrity": "sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz", + "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==", + "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.1.1" + "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/dom": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", - "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.2.tgz", + "integrity": "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==", + "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.0.0", - "@floating-ui/utils": "^0.2.0" + "@floating-ui/core": "^1.7.2", + "@floating-ui/utils": "^0.2.10" } }, - "node_modules/@floating-ui/dom/node_modules/@floating-ui/utils": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", - "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==", - "license": "MIT" + "node_modules/@floating-ui/react-dom": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } }, "node_modules/@floating-ui/utils": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", - "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" }, "node_modules/@formatjs/ecma402-abstract": { "version": "2.2.4", @@ -50452,7 +50462,7 @@ "@emotion/serialize": "^1.0.2", "@emotion/styled": "^11.6.0", "@emotion/utils": "^1.0.0", - "@floating-ui/react-dom": "^2.0.8", + "@floating-ui/react-dom": "2.0.8", "@types/gradient-parser": "0.1.3", "@types/highlight-words-core": "1.2.1", "@use-gesture/react": "^10.3.1", @@ -50499,18 +50509,6 @@ "react-dom": "^18.0.0" } }, - "packages/components/node_modules/@floating-ui/react-dom": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.1.tgz", - "integrity": "sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==", - "dependencies": { - "@floating-ui/dom": "^1.3.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, "packages/components/node_modules/@types/gradient-parser": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@types/gradient-parser/-/gradient-parser-0.1.3.tgz", diff --git a/packages/block-editor/src/components/inserter/media-tab/media-preview.js b/packages/block-editor/src/components/inserter/media-tab/media-preview.js index 319c25c01831c8..2b746d411214e9 100644 --- a/packages/block-editor/src/components/inserter/media-tab/media-preview.js +++ b/packages/block-editor/src/components/inserter/media-tab/media-preview.js @@ -36,7 +36,6 @@ import { getBlockAndPreviewFromMedia } from './utils'; import { store as blockEditorStore } from '../../../store'; const ALLOWED_MEDIA_TYPES = [ 'image' ]; -const MAXIMUM_TITLE_LENGTH = 25; const MEDIA_OPTIONS_POPOVER_PROPS = { position: 'bottom left', className: @@ -239,12 +238,6 @@ export function MediaPreview( { media, onClick, category } ) { ? media.title : media.title?.rendered || __( 'no title' ); - let truncatedTitle; - if ( title.length > MAXIMUM_TITLE_LENGTH ) { - const omission = '...'; - truncatedTitle = - title.slice( 0, MAXIMUM_TITLE_LENGTH - omission.length ) + omission; - } const onMouseEnter = useCallback( () => setIsHovered( true ), [] ); const onMouseLeave = useCallback( () => setIsHovered( false ), [] ); return ( @@ -268,7 +261,7 @@ export function MediaPreview( { media, onClick, category } ) { onMouseEnter={ onMouseEnter } onMouseLeave={ onMouseLeave } > - + + select( blockEditorStore ).hasSelectedInnerBlock( clientId, true ), + [ clientId ] + ); + + const handleSummaryKeyDown = ( event ) => { + if ( event.key === 'Enter' && ! event.shiftKey ) { + setIsOpen( ( prevIsOpen ) => ! prevIsOpen ); + event.preventDefault(); + } + }; + + // Prevent spacebar from toggling
while typing. + const handleSummaryKeyUp = ( event ) => { + if ( event.key === ' ' ) { + event.preventDefault(); + } + }; + return ( <> @@ -91,18 +118,21 @@ function DetailsEdit( { attributes, setAttributes } ) { ) } /> -
+
setIsOpen( event.target.open ) } + > { - event.preventDefault(); - setIsOpen( ! isOpen ); - } } + onKeyDown={ withIgnoreIMEEvents( handleSummaryKeyDown ) } + onKeyUp={ handleSummaryKeyUp } > diff --git a/packages/block-library/src/embed/variations.js b/packages/block-library/src/embed/variations.js index 82a4159e13b327..662599f1688950 100644 --- a/packages/block-library/src/embed/variations.js +++ b/packages/block-library/src/embed/variations.js @@ -251,14 +251,6 @@ const variations = [ patterns: [ /^https?:\/\/(www\.)?reverbnation\.com\/.+/i ], attributes: { providerNameSlug: 'reverbnation', responsive: true }, }, - { - name: 'screencast', - title: getTitle( 'Screencast' ), - icon: embedVideoIcon, - description: __( 'Embed Screencast content.' ), - patterns: [ /^https?:\/\/(www\.)?screencast\.com\/.+/i ], - attributes: { providerNameSlug: 'screencast', responsive: true }, - }, { name: 'scribd', title: getTitle( 'Scribd' ), diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index e08d0baff0503d..510e66a2676da4 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -262,10 +262,6 @@ export function ImageEdit( { additionalAttributes = { sizeSlug: newSize, }; - } else { - // Keep the same url when selecting the same file, so "Resolution" - // option is not changed. - additionalAttributes = { url }; } // Check if default link setting should be used. diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index e3018c274ef787..5e12bc0931d298 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -26,6 +26,7 @@ import { store as blockEditorStore, getColorClassName, useInnerBlocksProps, + useBlockEditingMode, } from '@wordpress/block-editor'; import { isURL, prependHTTP, safeDecodeURI } from '@wordpress/url'; import { useState, useEffect, useRef } from '@wordpress/element'; @@ -43,6 +44,10 @@ import { updateAttributes } from './update-attributes'; import { getColors } from '../navigation/edit/utils'; const DEFAULT_BLOCK = { name: 'core/navigation-link' }; +const NESTING_BLOCK_NAMES = [ + 'core/navigation-link', + 'core/navigation-submenu', +]; /** * A React hook to determine if it's dragging within the target element. @@ -95,19 +100,29 @@ const useIsDraggingWithin = ( elementRef ) => { return isDraggingWithin; }; -const useIsInvalidLink = ( kind, type, id ) => { +const useIsInvalidLink = ( kind, type, id, enabled ) => { const isPostType = kind === 'post-type' || type === 'post' || type === 'page'; const hasId = Number.isInteger( id ); + const blockEditingMode = useBlockEditingMode(); + const postStatus = useSelect( ( select ) => { if ( ! isPostType ) { return null; } + + // Fetching the posts status is an "expensive" operation. Especially for sites with large navigations. + // When the block is rendered in a template or other disabled contexts we can skip this check in order + // to avoid all these additional requests that don't really add any value in that mode. + if ( blockEditingMode === 'disabled' || ! enabled ) { + return null; + } + const { getEntityRecord } = select( coreStore ); return getEntityRecord( 'postType', type, id )?.status; }, - [ isPostType, type, id ] + [ isPostType, blockEditingMode, enabled, type, id ] ); // Check Navigation Link validity if: @@ -280,8 +295,6 @@ export default function NavigationLinkEdit( { clientId, } ) { const { id, label, type, url, description, kind } = attributes; - - const [ isInvalid, isDraft ] = useIsInvalidLink( kind, type, id ); const { maxNestingLevel } = context; const { @@ -313,6 +326,7 @@ export default function NavigationLinkEdit( { isTopLevelLink, isParentOfSelectedBlock, hasChildren, + validateLinkStatus, } = useSelect( ( select ) => { const { @@ -321,28 +335,48 @@ export default function NavigationLinkEdit( { getBlockRootClientId, hasSelectedInnerBlock, getBlockParentsByBlockName, + getSelectedBlockClientId, } = select( blockEditorStore ); + const rootClientId = getBlockRootClientId( clientId ); + const isTopLevel = + getBlockName( rootClientId ) === 'core/navigation'; + const selectedBlockClientId = getSelectedBlockClientId(); + const rootNavigationClientId = isTopLevel + ? rootClientId + : getBlockParentsByBlockName( + clientId, + 'core/navigation' + )[ 0 ]; + + // Enable when the root Navigation block is selected or any of its inner blocks. + const enableLinkStatusValidation = + selectedBlockClientId === rootNavigationClientId || + hasSelectedInnerBlock( rootNavigationClientId, true ); return { isAtMaxNesting: - getBlockParentsByBlockName( clientId, [ - 'core/navigation-link', - 'core/navigation-submenu', - ] ).length >= maxNestingLevel, - isTopLevelLink: - getBlockName( getBlockRootClientId( clientId ) ) === - 'core/navigation', + getBlockParentsByBlockName( clientId, NESTING_BLOCK_NAMES ) + .length >= maxNestingLevel, + isTopLevelLink: isTopLevel, isParentOfSelectedBlock: hasSelectedInnerBlock( clientId, true ), hasChildren: !! getBlockCount( clientId ), + validateLinkStatus: enableLinkStatusValidation, }; }, [ clientId, maxNestingLevel ] ); const { getBlocks } = useSelect( blockEditorStore ); + const [ isInvalid, isDraft ] = useIsInvalidLink( + kind, + type, + id, + validateLinkStatus + ); + /** * Transform to submenu block. */ diff --git a/packages/block-library/src/post-comments-form/style.scss b/packages/block-library/src/post-comments-form/style.scss index 69e6420581cb2f..b400849ba83bdb 100644 --- a/packages/block-library/src/post-comments-form/style.scss +++ b/packages/block-library/src/post-comments-form/style.scss @@ -82,9 +82,4 @@ margin-left: 0.5em; } } - - // Override the 100% width derived from the Button block - input[type="submit"] { - width: auto; - } } diff --git a/packages/block-library/src/post-template/edit.js b/packages/block-library/src/post-template/edit.js index 35d0a95501a1b2..0fbb16b00a8e97 100644 --- a/packages/block-library/src/post-template/edit.js +++ b/packages/block-library/src/post-template/edit.js @@ -181,14 +181,15 @@ export default function PostTemplateEdit( { * Handle cases where sticky is set to `exclude` or `only`. * Which works as a `post__in/post__not_in` query for sticky posts. */ - if ( sticky && sticky !== 'ignore' ) { + if ( [ 'exclude', 'only' ].includes( sticky ) ) { query.sticky = sticky === 'only'; } - if ( sticky === 'ignore' ) { + // Empty string represents the default behavior of including sticky posts. + if ( [ '', 'ignore' ].includes( sticky ) ) { // Remove any leftover sticky query parameter. delete query.sticky; - query.ignore_sticky = true; + query.ignore_sticky = sticky === 'ignore'; } // If `inherit` is truthy, adjust conditionally the query to create a better preview. diff --git a/packages/block-library/src/query/edit/inspector-controls/index.js b/packages/block-library/src/query/edit/inspector-controls/index.js index cf0b80b6145632..66ba962a074608 100644 --- a/packages/block-library/src/query/edit/inspector-controls/index.js +++ b/packages/block-library/src/query/edit/inspector-controls/index.js @@ -5,11 +5,12 @@ import { TextControl, SelectControl, RangeControl, - __experimentalToggleGroupControl as ToggleGroupControl, - __experimentalToggleGroupControlOption as ToggleGroupControlOption, Notice, + __experimentalVStack as VStack, __experimentalToolsPanel as ToolsPanel, __experimentalToolsPanelItem as ToolsPanelItem, + __experimentalToggleGroupControl as ToggleGroupControl, + __experimentalToggleGroupControlOption as ToggleGroupControlOption, } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; @@ -113,8 +114,7 @@ export default function QueryInspectorControls( props ) { }, [ querySearch, onChangeDebounced ] ); const orderByOptions = useOrderByOptions( postType ); - const showInheritControl = - ! isSingular && isControlAllowed( allowedControls, 'inherit' ); + const showInheritControl = isControlAllowed( allowedControls, 'inherit' ); const showPostTypeControl = ! inherit && isControlAllowed( allowedControls, 'postType' ); const postTypeControlLabel = __( 'Post type' ); @@ -185,6 +185,10 @@ export default function QueryInspectorControls( props ) { const showDisplayPanel = showPostCountControl || showOffSetControl || showPagesControl; + // The block cannot inherit a default WordPress query in singular content (e.g., post, page, 404, blank). + // Warn users but still permit this type of query for exceptional cases in Classic and Hybrid themes. + const hasInheritanceWarning = isSingular && inherit; + return ( <> { showSettingsPanel && ( @@ -208,36 +212,48 @@ export default function QueryInspectorControls( props ) { onDeselect={ () => setQuery( { inherit: true } ) } isShownByDefault > - { - setQuery( { - inherit: value === 'default', - } ); - } } - help={ - inherit - ? __( - 'Display a list of posts or custom post types based on the current template.' - ) - : __( - 'Display a list of posts or custom post types based on specific criteria.' - ) - } - value={ !! inherit ? 'default' : 'custom' } - > - - - + + { + setQuery( { + inherit: value === 'default', + } ); + } } + help={ + inherit + ? __( + 'Display a list of posts or custom post types based on the current template.' + ) + : __( + 'Display a list of posts or custom post types based on specific criteria.' + ) + } + value={ !! inherit ? 'default' : 'custom' } + > + + + + { hasInheritanceWarning && ( + + { __( + 'Cannot inherit the current template query when placed inside the singular content (e.g., post, page, 404, blank).' + ) } + + ) } + ) } diff --git a/packages/block-library/src/query/edit/query-content.js b/packages/block-library/src/query/edit/query-content.js index 459ce2af018c2a..f4a1d3d62e166d 100644 --- a/packages/block-library/src/query/edit/query-content.js +++ b/packages/block-library/src/query/edit/query-content.js @@ -98,21 +98,15 @@ export default function QueryContent( { } else if ( ! query.perPage && postsPerPage ) { newQuery.perPage = postsPerPage; } - // We need to reset the `inherit` value if in a singular template, as queries - // are not inherited when in singular content (e.g. post, page, 404, blank). - if ( isSingular && query.inherit ) { - newQuery.inherit = false; - } + if ( !! Object.keys( newQuery ).length ) { __unstableMarkNextChangeAsNotPersistent(); updateQuery( newQuery ); } }, [ query.perPage, - query.inherit, - postsPerPage, inherit, - isSingular, + postsPerPage, __unstableMarkNextChangeAsNotPersistent, updateQuery, ] ); diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 567cd3c0483db7..02a5056e0d9b70 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -7,6 +7,10 @@ - `ToggleGroupControl`: Fix active background for empty string value ([#69969](https://github.com/WordPress/gutenberg/pull/69969)). - `Button`: Fix tertiary variant displaying incorrect text color in pressed and hover states ([#68542](https://github.com/WordPress/gutenberg/pull/68542)). +### Internal + +- Mark `withIgnoreIMEEvents()` function as private API ([#70056](https://github.com/WordPress/gutenberg/pull/70056)). + ### Enhancement - `QueryControls`: Add menu_order sorting option if supported by the post type. ([#68781](https://github.com/WordPress/gutenberg/pull/68781)). diff --git a/packages/components/package.json b/packages/components/package.json index 5119fbabbea9cc..a7ef7c9c778f7a 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -40,7 +40,7 @@ "@emotion/serialize": "^1.0.2", "@emotion/styled": "^11.6.0", "@emotion/utils": "^1.0.0", - "@floating-ui/react-dom": "^2.0.8", + "@floating-ui/react-dom": "2.0.8", "@types/gradient-parser": "0.1.3", "@types/highlight-words-core": "1.2.1", "@use-gesture/react": "^10.3.1", diff --git a/packages/components/src/private-apis.ts b/packages/components/src/private-apis.ts index f5a9ee90519c2d..0a77ceca6e1b1c 100644 --- a/packages/components/src/private-apis.ts +++ b/packages/components/src/private-apis.ts @@ -7,6 +7,7 @@ import { ComponentsContext } from './context/context-system-provider'; import Theme from './theme'; import { Tabs } from './tabs'; import { kebabCase } from './utils/strings'; +import { withIgnoreIMEEvents } from './utils/with-ignore-ime-events'; import { lock } from './lock-unlock'; import Badge from './badge'; @@ -18,5 +19,6 @@ lock( privateApis, { Theme, Menu, kebabCase, + withIgnoreIMEEvents, Badge, } ); diff --git a/packages/dom/src/focusable.js b/packages/dom/src/focusable.js index ed0326fd264139..61b3c800da1853 100644 --- a/packages/dom/src/focusable.js +++ b/packages/dom/src/focusable.js @@ -39,6 +39,7 @@ function buildSelector( sequential ) { 'iframe:not([tabindex^="-"])', 'object', 'embed', + 'summary', 'area[href]', '[contenteditable]:not([contenteditable=false])', ].join( ',' ); diff --git a/packages/url/README.md b/packages/url/README.md index 0e4628669d11f2..ae5b6bf945fe5f 100644 --- a/packages/url/README.md +++ b/packages/url/README.md @@ -68,7 +68,7 @@ _Returns_ Performs some basic cleanup of a string for use as a post slug. -This replicates some of what `sanitize_title()` does in WordPress core, but is only designed to approximate what the slug will be. +This replicates some of what `sanitize_title_with_dashes()` does in WordPress core, but is only designed to approximate what the slug will be. Converts Latin-1 Supplement and Latin Extended-A letters to basic Latin letters. Removes combining diacritical marks. Converts whitespace, periods, and forward slashes to hyphens. Removes any remaining non-word characters except hyphens. Converts remaining string to lowercase. It does not account for octets, HTML entities, or other encoded characters. diff --git a/packages/url/src/clean-for-slug.js b/packages/url/src/clean-for-slug.js index 0cee207073c3d0..e288f5949537ad 100644 --- a/packages/url/src/clean-for-slug.js +++ b/packages/url/src/clean-for-slug.js @@ -6,7 +6,7 @@ import removeAccents from 'remove-accents'; /** * Performs some basic cleanup of a string for use as a post slug. * - * This replicates some of what `sanitize_title()` does in WordPress core, but + * This replicates some of what `sanitize_title_with_dashes()` does in WordPress core, but * is only designed to approximate what the slug will be. * * Converts Latin-1 Supplement and Latin Extended-A letters to basic Latin @@ -25,8 +25,12 @@ export function cleanForSlug( string ) { } return ( removeAccents( string ) + // Convert  , &ndash, and &mdash to hyphens. + .replace( /( |–|—)/g, '-' ) // Convert each group of whitespace, periods, and forward slashes to a hyphen. .replace( /[\s\./]+/g, '-' ) + // Remove all HTML entities. + .replace( /&\S+?;/g, '' ) // Remove anything that's not a letter, number, underscore or hyphen. .replace( /[^\p{L}\p{N}_-]+/gu, '' ) // Convert to lowercase diff --git a/packages/url/src/test/index.js b/packages/url/src/test/index.js index 763c2e71c9fcbe..0be7c7542b71c8 100644 --- a/packages/url/src/test/index.js +++ b/packages/url/src/test/index.js @@ -1202,6 +1202,24 @@ describe( 'cleanForSlug', () => { expect( cleanForSlug( 'the long - cat' ) ).toBe( 'the-long-cat' ); expect( cleanForSlug( 'the----long---cat' ) ).toBe( 'the-long-cat' ); } ); + + it( 'Should remove ampersands', () => { + expect( cleanForSlug( 'the long cat & dog' ) ).toBe( + 'the-long-cat-dog' + ); + expect( + cleanForSlug( 'the long cat & a dog && fish' ) + ).toBe( 'the-long-cat-a-dog-fish' ); + expect( cleanForSlug( 'the long cat &amp; dog' ) ).toBe( + 'the-long-cat-amp-dog' + ); + } ); + + it( 'Should remove HTML entities', () => { + expect( + cleanForSlug( 'No   Entities> – Here —<' ) + ).toBe( 'no-entities-here' ); + } ); } ); describe( 'normalizePath', () => { diff --git a/test/e2e/specs/editor/blocks/details.spec.js b/test/e2e/specs/editor/blocks/details.spec.js new file mode 100644 index 00000000000000..2b7e954b6eeaad --- /dev/null +++ b/test/e2e/specs/editor/blocks/details.spec.js @@ -0,0 +1,143 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Details', () => { + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost(); + } ); + + test( 'can toggle hidden blocks by pressing enter', async ( { + editor, + page, + } ) => { + // Insert a details block with empty inner blocks. + await editor.insertBlock( { + name: 'core/details', + attributes: { + summary: 'Details summary', + }, + innerBlocks: [ + { + name: 'core/paragraph', + attributes: { + content: 'Details content', + }, + }, + ], + } ); + + // Open the details block. + await page.keyboard.press( 'Enter' ); + + // The inner block should be visible. + await expect( + editor.canvas.getByRole( 'document', { name: 'Block: Paragraph' } ) + ).toContainText( 'Details content' ); + + // Close the details block. + await page.keyboard.press( 'Enter' ); + + // The inner block should be hidden. + await expect( + editor.canvas.getByRole( 'document', { name: 'Block: Paragraph' } ) + ).toBeHidden(); + } ); + + test( 'can create a multiline summary with Shift+Enter', async ( { + editor, + page, + } ) => { + // Insert a details block. + await editor.insertBlock( { + name: 'core/details', + } ); + + const summary = editor.canvas.getByRole( 'textbox', { + name: 'Write summary', + } ); + + // Add a multiline summary. + await summary.type( 'First line' ); + await page.keyboard.press( 'Shift+Enter' ); + await summary.type( 'Second line' ); + + // Verify the summary is multiline. + await expect( summary ).toHaveText( 'First line\nSecond line', { + useInnerText: true, + } ); + } ); + + test( 'typing space in summary rich-text should not toggle details', async ( { + editor, + } ) => { + // Insert a details block. + await editor.insertBlock( { + name: 'core/details', + } ); + + const summary = editor.canvas.getByRole( 'textbox', { + name: 'Write summary', + } ); + + // Type space in the summary rich-text. + await summary.type( ' ' ); + + // Verify the details block is not toggled. + await expect( + editor.canvas.getByRole( 'document', { name: 'Empty block' } ) + ).toBeHidden(); + } ); + + test( 'selecting hidden blocks in list view expands details and focuses content', async ( { + editor, + page, + pageUtils, + } ) => { + // Insert a details block. + await editor.insertBlock( { + name: 'core/details', + attributes: { + summary: 'Details summary', + }, + innerBlocks: [ + { + name: 'core/paragraph', + attributes: { + content: 'Details content', + }, + }, + ], + } ); + + const listView = page.getByRole( 'treegrid', { + name: 'Block navigation structure', + } ); + + // Open the list view. + await pageUtils.pressKeys( 'access+o' ); + + // Verify inner blocks appear in the list view. + await page.keyboard.press( 'ArrowRight' ); + await expect( + listView.getByRole( 'link', { + name: 'Paragraph', + } ) + ).toBeVisible(); + + // Verify the first inner block in the list view is focused. + await page.keyboard.press( 'ArrowDown' ); + await expect( + listView.getByRole( 'link', { + name: 'Paragraph', + } ) + ).toBeFocused(); + + // Verify the first inner block in the editor canvas is focused. + await page.keyboard.press( 'Enter' ); + await expect( + editor.canvas.getByRole( 'document', { name: 'Block: Paragraph' } ) + ).toBeFocused(); + } ); +} );