Skip to content

Commit 77151c7

Browse files
ellatrixclaude
andcommitted
Only host editable roots among fully editable siblings
Consolidate the hosting conditions into canHostEditableRoot, shared by the hook and the selection observer (whose copy had drifted and lacked the editing mode check), and require every sibling in the list to have the default editing mode. A host makes all its content editable by inheritance: with an override-enabled pattern paragraph selected, the pattern's read-only content became editable, and clicking it set off a focus chain that moved the selection to the paragraph, so the button's link edit was applied to the wrong block. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 2cbfed6 commit 77151c7

2 files changed

Lines changed: 58 additions & 47 deletions

File tree

packages/block-editor/src/components/writing-flow/use-editable-root.js

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,53 @@ import { store as blockEditorStore } from '../../store';
1313
import { getBlockClientId, getSelectionEditableElement } from '../../utils/dom';
1414
import { unlock } from '../../lock-unlock';
1515

16+
/**
17+
* Returns true when the writing flow wrapper can host editing for the given
18+
* block: it supports `editableRoot`, is edited visually in the default
19+
* editing mode, and has sibling blocks for a native selection to extend
20+
* into, all of them editable.
21+
*
22+
* @param {Object} select Bound block editor store selectors.
23+
* @param {string} clientId Block client ID.
24+
*
25+
* @return {boolean} Whether an editing host can host the block.
26+
*/
27+
export function canHostEditableRoot( select, clientId ) {
28+
const {
29+
getBlockName,
30+
getBlockEditingMode,
31+
getBlockMode,
32+
getBlockRootClientId,
33+
getBlockOrder,
34+
} = select;
35+
36+
if (
37+
! clientId ||
38+
getBlockEditingMode( clientId ) !== 'default' ||
39+
// Not when the block is edited as HTML: there is no rich text to
40+
// host then, only a textarea, which the editing host would
41+
// interfere with.
42+
getBlockMode( clientId ) !== 'visual' ||
43+
! hasBlockSupport( getBlockName( clientId ), 'editableRoot', false )
44+
) {
45+
return false;
46+
}
47+
48+
// Only host when the block has sibling blocks for a native selection to
49+
// extend into, all of them editable. A lone block (e.g. a single
50+
// paragraph nested in an HTML block) is edited on its own element, and
51+
// read-only siblings (e.g. pattern content without overrides enabled)
52+
// must not become editable by inheriting from the host.
53+
const siblings = getBlockOrder( getBlockRootClientId( clientId ) );
54+
return (
55+
siblings.length > 1 &&
56+
siblings.every(
57+
( siblingClientId ) =>
58+
getBlockEditingMode( siblingClientId ) === 'default'
59+
)
60+
);
61+
}
62+
1663
/**
1764
* Returns true when the writing flow wrapper should be contentEditable: the
1865
* selected block supports `editableRoot`.
@@ -21,32 +68,10 @@ import { unlock } from '../../lock-unlock';
2168
*/
2269
export function useHasEditableRoot() {
2370
return useSelect( ( select ) => {
24-
const {
25-
getSelectedBlockClientId,
26-
getBlockName,
27-
getBlockEditingMode,
28-
getBlockMode,
29-
getBlockRootClientId,
30-
getBlockOrder,
31-
} = select( blockEditorStore );
32-
const clientId = getSelectedBlockClientId();
33-
return (
34-
!! clientId &&
35-
getBlockEditingMode( clientId ) === 'default' &&
36-
// Not when the block is edited as HTML: there is no rich text
37-
// to host then, only a textarea, which the editing host would
38-
// interfere with.
39-
getBlockMode( clientId ) === 'visual' &&
40-
hasBlockSupport(
41-
getBlockName( clientId ),
42-
'editableRoot',
43-
false
44-
) &&
45-
// Only host when the block has sibling blocks for a native
46-
// selection to extend into. A lone block (e.g. a single
47-
// paragraph nested in an HTML block) is edited on its own
48-
// element, not through a canvas-wide editing host.
49-
getBlockOrder( getBlockRootClientId( clientId ) ).length > 1
71+
const selectors = select( blockEditorStore );
72+
return canHostEditableRoot(
73+
selectors,
74+
selectors.getSelectedBlockClientId()
5075
);
5176
}, [] );
5277
}

packages/block-editor/src/components/writing-flow/use-selection-observer.js

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ import {
88
privateApis as richTextPrivateApis,
99
} from '@wordpress/rich-text';
1010
import { isSelectionForward } from '@wordpress/dom';
11-
import { hasBlockSupport } from '@wordpress/blocks';
1211

1312
/**
1413
* Internal dependencies
1514
*/
1615
import { store as blockEditorStore } from '../../store';
1716
import { getBlockClientId } from '../../utils/dom';
17+
import { canHostEditableRoot } from './use-editable-root';
1818
import { unlock } from '../../lock-unlock';
1919

2020
const { ownsSelection } = unlock( richTextPrivateApis );
@@ -132,18 +132,15 @@ function getRichTextElement( node ) {
132132
export default function useSelectionObserver() {
133133
const { multiSelect, selectBlock, selectionChange } =
134134
useDispatch( blockEditorStore );
135+
const blockEditorSelectors = useSelect( blockEditorStore );
135136
const {
136137
getBlockParents,
137138
getBlockSelectionStart,
138139
isMultiSelecting,
139-
getBlockName,
140-
getBlockMode,
141140
getSelectionStart,
142141
getSelectionEnd,
143142
getSelectedBlockClientId,
144-
getBlockRootClientId,
145-
getBlockOrder,
146-
} = useSelect( blockEditorStore );
143+
} = blockEditorSelectors;
147144
return useRefEffect(
148145
( node ) => {
149146
const { ownerDocument } = node;
@@ -212,21 +209,10 @@ export default function useSelectionObserver() {
212209
// always move it), which must not re-enable the wrapper
213210
// after another block has been selected.
214211
collapsedClientId === getSelectedBlockClientId() &&
215-
// Not while the block is edited as HTML: its content is
216-
// a textarea, not rich text, which the editing host
217-
// would interfere with.
218-
getBlockMode( collapsedClientId ) === 'visual' &&
219-
hasBlockSupport(
220-
getBlockName( collapsedClientId ),
221-
'editableRoot',
222-
false
223-
) &&
224-
// Only host when the block has sibling blocks for a
225-
// native selection to extend into; a lone block is
226-
// edited on its own element.
227-
getBlockOrder(
228-
getBlockRootClientId( collapsedClientId )
229-
).length > 1
212+
canHostEditableRoot(
213+
blockEditorSelectors,
214+
collapsedClientId
215+
)
230216
) {
231217
setContentEditableWrapper( node, true );
232218

0 commit comments

Comments
 (0)