Skip to content

Commit 4d1baac

Browse files
committed
Refactor navigation link read-only control to use binding source canUserEditValue
Replaces the custom readOnly attribute with the Gutenberg-native canUserEditValue mechanism from block binding sources. This allows binding sources to control whether navigation links are editable, enabling custom sources to create truly read-only links while keeping core bindings editable. Changes: - Remove readOnly attribute from navigation-link block.json - Derive isLinkEditable from binding sources' canUserEditValue - Update core/post-data and core/term-data to return true for navigation blocks - Prevent overwriting custom binding sources with core bindings - Update documentation
1 parent a9147f3 commit 4d1baac

File tree

6 files changed

+74
-37
lines changed

6 files changed

+74
-37
lines changed

docs/reference-guides/core-blocks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -550,7 +550,7 @@ Add a page, link, or another item to your navigation. ([Source](https://github.c
550550
- **Parent:** core/navigation
551551
- **Allowed Blocks:** core/navigation-link, core/navigation-submenu, core/page-list
552552
- **Supports:** anchor, interactivity (clientNavigation), typography (fontSize, lineHeight), ~~html~~, ~~renaming~~, ~~reusable~~
553-
- **Attributes:** description, id, isTopLevelLink, kind, label, opensInNewTab, readOnly, rel, title, type, url
553+
- **Attributes:** description, id, isTopLevelLink, kind, label, opensInNewTab, rel, title, type, url
554554

555555
## Navigation Overlay Close
556556

packages/block-library/src/navigation-link/block.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,6 @@
4545
},
4646
"isTopLevelLink": {
4747
"type": "boolean"
48-
},
49-
"readOnly": {
50-
"type": "boolean",
51-
"default": false
5248
}
5349
},
5450
"usesContext": [

packages/block-library/src/navigation-link/edit.js

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import clsx from 'clsx';
66
/**
77
* WordPress dependencies
88
*/
9-
import { createBlock } from '@wordpress/blocks';
9+
import { createBlock, getBlockBindingsSource } from '@wordpress/blocks';
1010
import { useSelect, useDispatch } from '@wordpress/data';
1111
import {
1212
ToolbarButton,
@@ -90,20 +90,15 @@ export default function NavigationLinkEdit( {
9090
context,
9191
clientId,
9292
} ) {
93-
const { id, label, type, url, description, kind, metadata, readOnly } =
94-
attributes;
93+
const { id, label, type, url, description, kind, metadata } = attributes;
9594
const { maxNestingLevel } = context;
9695

9796
const {
9897
replaceBlock,
9998
__unstableMarkNextChangeAsNotPersistent,
10099
selectBlock,
101100
} = useDispatch( blockEditorStore );
102-
// Have the link editing ui open on mount when lacking a url and selected.
103-
// Don't open if readOnly is true.
104-
const [ isLinkOpen, setIsLinkOpen ] = useState(
105-
isSelected && ! url && ! readOnly
106-
);
101+
107102
// Use internal state instead of a ref to make sure that the component
108103
// re-renders when the popover's anchor updates.
109104
const [ popoverAnchor, setPopoverAnchor ] = useState( null );
@@ -177,6 +172,52 @@ export default function NavigationLinkEdit( {
177172
attributes,
178173
} );
179174

175+
// Check if bindings allow editing
176+
const { canEditUrl, canEditId } = useSelect(
177+
( select ) => {
178+
const urlBinding = metadata?.bindings?.url;
179+
const idBinding = metadata?.bindings?.id;
180+
181+
const urlBindingSource = urlBinding
182+
? getBlockBindingsSource( urlBinding.source )
183+
: null;
184+
const idBindingSource = idBinding
185+
? getBlockBindingsSource( idBinding.source )
186+
: null;
187+
188+
const canEditUrlResult =
189+
! urlBinding ||
190+
urlBindingSource?.canUserEditValue?.( {
191+
select,
192+
context,
193+
args: urlBinding?.args,
194+
} ) !== false;
195+
196+
const canEditIdResult =
197+
! idBinding ||
198+
idBindingSource?.canUserEditValue?.( {
199+
select,
200+
context,
201+
args: idBinding?.args,
202+
} ) !== false;
203+
204+
return {
205+
canEditUrl: canEditUrlResult,
206+
canEditId: canEditIdResult,
207+
};
208+
},
209+
[ metadata?.bindings, context ]
210+
);
211+
212+
// Link is editable only if both URL and ID are editable
213+
const isLinkEditable = canEditUrl && canEditId;
214+
215+
// Have the link editing ui open on mount when lacking a url and selected.
216+
// Don't open if link is not editable (based on binding source's canUserEditValue).
217+
const [ isLinkOpen, setIsLinkOpen ] = useState(
218+
isSelected && ! url && isLinkEditable
219+
);
220+
180221
const handleLinkChange = useHandleLinkChange( {
181222
clientId,
182223
attributes,
@@ -360,7 +401,7 @@ export default function NavigationLinkEdit( {
360401
isDraft ||
361402
( hasUrlBinding && ! isBoundEntityAvailable );
362403

363-
if ( needsValidLink && ! readOnly ) {
404+
if ( needsValidLink && isLinkEditable ) {
364405
blockProps.onClick = () => {
365406
setIsLinkOpen( true );
366407
};
@@ -383,11 +424,11 @@ export default function NavigationLinkEdit( {
383424
title={ __( 'Link' ) }
384425
shortcut={ displayShortcut.primary( 'k' ) }
385426
onClick={ () => {
386-
if ( ! readOnly ) {
427+
if ( isLinkEditable ) {
387428
setIsLinkOpen( true );
388429
}
389430
} }
390-
disabled={ readOnly }
431+
disabled={ ! isLinkEditable }
391432
/>
392433
{ ! isAtMaxNesting && (
393434
<ToolbarButton
@@ -404,7 +445,7 @@ export default function NavigationLinkEdit( {
404445
attributes={ attributes }
405446
setAttributes={ setAttributes }
406447
clientId={ clientId }
407-
isLinkEditable={ ! readOnly }
448+
isLinkEditable={ isLinkEditable }
408449
/>
409450
</InspectorControls>
410451
<div { ...blockProps }>

packages/block-library/src/navigation-link/shared/use-entity-binding.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,16 @@ export function useEntityBinding( { clientId, attributes } ) {
141141
return;
142142
}
143143

144+
// Don't override custom binding sources - only create core bindings for post-type/taxonomy
145+
const currentSource = metadata?.bindings?.url?.source;
146+
if (
147+
currentSource &&
148+
currentSource !== 'core/post-data' &&
149+
currentSource !== 'core/term-data'
150+
) {
151+
return;
152+
}
153+
144154
try {
145155
const binding = buildNavigationLinkEntityBinding( kindToUse );
146156
updateBlockBindings( binding );
@@ -153,7 +163,7 @@ export function useEntityBinding( { clientId, attributes } ) {
153163
// Don't create binding if validation fails.
154164
}
155165
},
156-
[ updateBlockBindings, kind ]
166+
[ updateBlockBindings, kind, metadata?.bindings?.url?.source ]
157167
);
158168

159169
return {

packages/editor/src/bindings/post-data.js

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { __ } from '@wordpress/i18n';
55
import { store as coreDataStore } from '@wordpress/core-data';
66
import { store as blockEditorStore } from '@wordpress/block-editor';
77

8-
// Navigation block types that use special handling for backwards compatibility
8+
// Navigation block types that store entity data in block attributes instead of context
99
const NAVIGATION_BLOCK_TYPES = [
1010
'core/navigation-link',
1111
'core/navigation-submenu',
@@ -84,16 +84,7 @@ export default {
8484
}
8585
return newValues;
8686
},
87-
setValues( { dispatch, context, bindings, clientId, select } ) {
88-
const { getBlockName } = select( blockEditorStore );
89-
90-
const blockName = getBlockName( clientId );
91-
92-
// Navigaton block types are read-only.
93-
// See https://github.com/WordPress/gutenberg/pull/72165.
94-
if ( NAVIGATION_BLOCK_TYPES.includes( blockName ) ) {
95-
return false;
96-
}
87+
setValues( { dispatch, context, bindings } ) {
9788
const newData = {};
9889
Object.values( bindings ).forEach( ( { args, newValue } ) => {
9990
newData[ args.field ] = newValue;
@@ -112,10 +103,10 @@ export default {
112103
const clientId = getSelectedBlockClientId();
113104
const blockName = getBlockName( clientId );
114105

115-
// Navigaton block types are read-only.
116-
// See https://github.com/WordPress/gutenberg/pull/72165.
106+
// Navigation blocks manage entity data through block attributes, not context.
107+
// They should always be editable when bound to core/post-data.
117108
if ( NAVIGATION_BLOCK_TYPES.includes( blockName ) ) {
118-
return false;
109+
return true;
119110
}
120111

121112
// Lock editing in query loop.

packages/editor/src/bindings/term-data.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { __ } from '@wordpress/i18n';
55
import { store as coreDataStore } from '@wordpress/core-data';
66
import { store as blockEditorStore } from '@wordpress/block-editor';
77

8-
// Navigation block types that use special handling for backwards compatibility
8+
// Navigation block types that store entity data in block attributes instead of context
99
const NAVIGATION_BLOCK_TYPES = [
1010
'core/navigation-link',
1111
'core/navigation-submenu',
@@ -129,14 +129,13 @@ export default {
129129
canUserEditValue( { select, context } ) {
130130
const { getBlockName, getSelectedBlockClientId } =
131131
select( blockEditorStore );
132-
133132
const clientId = getSelectedBlockClientId();
134133
const blockName = getBlockName( clientId );
135134

136-
// Navigaton block types are read-only.
137-
// See https://github.com/WordPress/gutenberg/pull/72165.
135+
// Navigation blocks manage entity data through block attributes, not context.
136+
// They should always be editable when bound to core/term-data.
138137
if ( NAVIGATION_BLOCK_TYPES.includes( blockName ) ) {
139-
return false;
138+
return true;
140139
}
141140

142141
// Terms are typically read-only when displayed.

0 commit comments

Comments
 (0)