diff --git a/packages/sanity/playwright-ct/tests/formBuilder/inputs/PortableText/FocusTracking.spec.tsx b/packages/sanity/playwright-ct/tests/formBuilder/inputs/PortableText/FocusTracking.spec.tsx index 447ede9b85e..600c8313ee3 100644 --- a/packages/sanity/playwright-ct/tests/formBuilder/inputs/PortableText/FocusTracking.spec.tsx +++ b/packages/sanity/playwright-ct/tests/formBuilder/inputs/PortableText/FocusTracking.spec.tsx @@ -3,6 +3,7 @@ import {expect, test} from '@playwright/experimental-ct-react' import {type Page} from '@playwright/test' import {type Path, type SanityDocument} from '@sanity/types' +import {testHelpers} from '../../../utils/testHelpers' import FocusTrackingStory from './FocusTrackingStory' export type UpdateFn = () => {focusPath: Path; document: SanityDocument} @@ -136,6 +137,24 @@ test.describe('Portable Text Input', () => { await expect(blockObjectInput).not.toBeVisible() }) }) + test(`reports focus on spans with with .text prop, and everything else without`, async ({ + mount, + page, + }) => { + const paths: Path[] = [] + const pushPath = (path: Path) => paths.push(path) + await mount() + const {getFocusedPortableTextEditor} = testHelpers({page}) + const $pte = await getFocusedPortableTextEditor('field-body') + await expect($pte).toBeFocused() + expect(paths.slice(-1)[0]).toEqual(['body', {_key: 'a'}, 'children', {_key: 'b'}, 'text']) + const $inlineObject = page.getByTestId('inline-preview') + await $inlineObject.click() + expect(paths.slice(-1)[0]).toEqual(['body', {_key: 'g'}, 'children', {_key: 'i'}]) + const $blockObject = page.getByTestId('pte-block-object') + await $blockObject.click() + expect(paths.slice(-1)[0]).toEqual(['body', {_key: 'k'}]) + }) }) function waitForFocusedNodeText(page: Page, text: string) { diff --git a/packages/sanity/playwright-ct/tests/formBuilder/inputs/PortableText/FocusTrackingStory.tsx b/packages/sanity/playwright-ct/tests/formBuilder/inputs/PortableText/FocusTrackingStory.tsx index a5c2e2edd0d..0d9687c9729 100644 --- a/packages/sanity/playwright-ct/tests/formBuilder/inputs/PortableText/FocusTrackingStory.tsx +++ b/packages/sanity/playwright-ct/tests/formBuilder/inputs/PortableText/FocusTrackingStory.tsx @@ -57,14 +57,16 @@ const SCHEMA_TYPES = [ export function FocusTrackingStory({ focusPath, + onPathFocus, document, }: { focusPath?: Path + onPathFocus?: (path: Path) => void document?: SanityDocument }) { return ( - + ) } diff --git a/packages/sanity/playwright-ct/tests/formBuilder/utils/TestForm.tsx b/packages/sanity/playwright-ct/tests/formBuilder/utils/TestForm.tsx index f9dea6b8586..523065c5919 100644 --- a/packages/sanity/playwright-ct/tests/formBuilder/utils/TestForm.tsx +++ b/packages/sanity/playwright-ct/tests/formBuilder/utils/TestForm.tsx @@ -34,10 +34,12 @@ declare global { export function TestForm({ focusPath: focusPathFromProps, + onPathFocus: onPathFocusFromProps, document: documentFromProps, id: idFromProps = 'root', }: { focusPath?: Path + onPathFocus?: (path: Path) => void document?: SanityDocument id?: string }) { @@ -115,8 +117,9 @@ export function TestForm({ const handleFocus = useCallback( (nextFocusPath: Path) => { setFocusPath(nextFocusPath) + onPathFocusFromProps?.(nextFocusPath) }, - [setFocusPath], + [onPathFocusFromProps], ) const handleBlur = useCallback(() => { diff --git a/packages/sanity/src/core/form/inputs/PortableText/PortableTextInput.tsx b/packages/sanity/src/core/form/inputs/PortableText/PortableTextInput.tsx index 856bb23db59..0ef1329c7ab 100644 --- a/packages/sanity/src/core/form/inputs/PortableText/PortableTextInput.tsx +++ b/packages/sanity/src/core/form/inputs/PortableText/PortableTextInput.tsx @@ -146,22 +146,27 @@ export function PortableTextInput(props: PortableTextInputProps) { } }, [hasFocusWithin]) + // Report focus on spans with `.text` appended to the reported focusPath. + // This is done to support the Presentation tool which uses this kind of paths to refer to texts. + // The PT-input already supports these paths the other way around. + // It's a bit ugly right here, but it's a rather simple way to support the Presentation tool without + // having to change the PTE's internals. const setFocusPathFromEditorSelection = useCallback( (focusPath: Path) => { - // Report focus on spans with `.text` appended to the reported focusPath. - // This is done to support the Presentation tool which uses this kind of paths to refer to texts. - // The PT-input already supports these paths the other way around. - // It's a bit ugly right here, but it's a rather simple way to support the Presentation tool without - // having to change the PTE's internals. - if ( - focusPath.length === 3 && - focusPath[1] === 'children' && - isKeySegment(focusPath[2]) && + // Test if the focusPath is pointing directly to a span + const isSpanPath = + focusPath.length === 3 && // A span path is always 3 segments long + focusPath[1] === 'children' && // Is a child of a block + isKeySegment(focusPath[2]) && // Contains the key of the child !portableTextMemberItems.some( - (item) => isKeySegment(focusPath[2]) && item.key === focusPath[2]._key, - ) // Not an inline object - ) { + (item) => isKeySegment(focusPath[2]) && item.member.key === focusPath[2]._key, + ) // Not an inline object (it would be a member in this list, where spans are not). By doing this check we avoid depending on the value. + if (isSpanPath) { + // Append `.text` to the focusPath onPathFocus(focusPath.concat('text')) + } else { + // Call normally + onPathFocus(focusPath) } }, [onPathFocus, portableTextMemberItems],