Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion packages/rich-text/src/component/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,28 @@ export function useRichText( {
}

function applyFromProps() {
// Get previous value before updating
const previousValue = _valueRef.current;

setRecordFromProps();
applyRecord( recordRef.current );

// Check if content length changed (text was added/removed, not just formatted)
const contentLengthChanged =
previousValue &&
typeof previousValue === 'string' &&
typeof value === 'string' &&
previousValue.length !== value.length;

// Check if focus is on this element
const hasFocus = ref.current?.contains(
ref.current.ownerDocument.activeElement
);
Comment on lines +166 to +169
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@scruffian We could potentially remove this condition as well. It's more of a defensive addition to check where focus is coming from.


// Skip re-applying the selection state when content changed from external source
// (e.g., typing in sidebar input changes canvas text)
const skipSelection = contentLengthChanged && ! hasFocus;

applyRecord( recordRef.current, { domOnly: skipSelection } );
}

const didMountRef = useRef( false );
Expand Down
87 changes: 87 additions & 0 deletions test/e2e/specs/editor/blocks/navigation.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,93 @@ test.describe( 'Navigation block', () => {
await pageUtils.pressKeys( 'ArrowDown' );
await expect( navigation.getNavBlockInserter() ).toBeFocused();
} );

test( 'should preserve focus in sidebar text input when typing (@firefox)', async ( {
page,
editor,
requestUtils,
pageUtils,
} ) => {
// Create a navigation menu with one link
const createdMenu = await requestUtils.createNavigationMenu( {
title: 'Test Menu',
content: `<!-- wp:navigation-link {"label":"Home","url":"https://example.com"} /-->`,
} );

// Insert the navigation block
await editor.insertBlock( {
name: 'core/navigation',
attributes: {
ref: createdMenu?.id,
},
} );

// Click on the navigation link label in the canvas to edit it
const linkLabel = editor.canvas.getByRole( 'textbox', {
name: 'Navigation link text',
} );
await linkLabel.click();
await pageUtils.pressKeys( 'primary+a' );
await page.keyboard.type( 'Updated Home' );

// Open the document settings sidebar
await editor.openDocumentSettingsSidebar();

// Tab to the sidebar settings panel
// First tab should go to the settings sidebar
await page.keyboard.press( 'Tab' );

// Find the text input in the sidebar
const textInput = page.getByRole( 'textbox', {
name: 'Text',
} );

// Tab until we reach the Text field in the sidebar
// This may take multiple tabs depending on other controls
for ( let i = 0; i < 10; i++ ) {
const focusedElement = await page.evaluate( () => {
const el = document.activeElement;
return {
tagName: el?.tagName,
label:
el?.getAttribute( 'aria-label' ) ||
el?.labels?.[ 0 ]?.textContent,
id: el?.id,
};
} );

if (
focusedElement.label?.includes( 'Text' ) &&
focusedElement.tagName === 'INPUT'
) {
break;
}

await page.keyboard.press( 'Tab' );
}

await expect( textInput ).toBeFocused();
await pageUtils.pressKeys( 'ArrowRight' );
// Type in the sidebar text input
await page.keyboard.type( ' Extra' );

// Verify the text was actually typed (change happened)
await expect( textInput ).toHaveValue( 'Updated Home Extra' );

// Tab again to move to the next field
await page.keyboard.press( 'Tab' );

// Check that focus is still within the document sidebar
const focusIsInSidebar = await page.evaluate( () => {
const activeEl = document.activeElement;
const sidebar = document.querySelector(
'.interface-interface-skeleton__sidebar'
);
return sidebar?.contains( activeEl );
} );

expect( focusIsInSidebar ).toBe( true );
} );
} );

test( 'Adding new links to a navigation block with existing inner blocks triggers creation of a single Navigation Menu', async ( {
Expand Down
Loading