@wordpress/block-library 9.36.1-next.8b30e05b0.0 → 9.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/audio/index.js +1 -1
  3. package/build/audio/index.js.map +2 -2
  4. package/build/avatar/edit.js +13 -38
  5. package/build/avatar/edit.js.map +2 -2
  6. package/build/breadcrumbs/block.json +3 -2
  7. package/build/breadcrumbs/edit.js +18 -18
  8. package/build/breadcrumbs/edit.js.map +2 -2
  9. package/build/button/block.json +2 -3
  10. package/build/button/deprecated.js +203 -0
  11. package/build/button/deprecated.js.map +3 -3
  12. package/build/button/edit.js +13 -24
  13. package/build/button/edit.js.map +3 -3
  14. package/build/button/index.js +1 -1
  15. package/build/button/index.js.map +2 -2
  16. package/build/button/save.js +0 -2
  17. package/build/button/save.js.map +2 -2
  18. package/build/code/index.js +1 -1
  19. package/build/code/index.js.map +2 -2
  20. package/build/comments-pagination-numbers/block.json +7 -0
  21. package/build/cover/index.js +2 -2
  22. package/build/cover/index.js.map +2 -2
  23. package/build/details/index.js +1 -1
  24. package/build/details/index.js.map +2 -2
  25. package/build/file/index.js +1 -1
  26. package/build/file/index.js.map +2 -2
  27. package/build/freeform/block.json +0 -1
  28. package/build/gallery/edit.js +1 -1
  29. package/build/gallery/edit.js.map +2 -2
  30. package/build/gallery/transforms.js +7 -3
  31. package/build/gallery/transforms.js.map +2 -2
  32. package/build/heading/index.js +1 -1
  33. package/build/heading/index.js.map +2 -2
  34. package/build/html/modal.js +18 -7
  35. package/build/html/modal.js.map +2 -2
  36. package/build/image/index.js +1 -1
  37. package/build/image/index.js.map +2 -2
  38. package/build/index.js +9 -3
  39. package/build/index.js.map +2 -2
  40. package/build/list-item/index.js +1 -1
  41. package/build/list-item/index.js.map +2 -2
  42. package/build/math/edit.js +14 -2
  43. package/build/math/edit.js.map +2 -2
  44. package/build/media-text/index.js +1 -1
  45. package/build/media-text/index.js.map +2 -2
  46. package/build/more/index.js +1 -1
  47. package/build/more/index.js.map +2 -2
  48. package/build/navigation/block.json +3 -0
  49. package/build/navigation/edit/index.js +57 -83
  50. package/build/navigation/edit/index.js.map +3 -3
  51. package/build/navigation/edit/overlay-menu-preview-button.js +96 -0
  52. package/build/navigation/edit/overlay-menu-preview-button.js.map +7 -0
  53. package/build/navigation/edit/overlay-menu-preview-controls.js +90 -0
  54. package/build/navigation/edit/overlay-menu-preview-controls.js.map +7 -0
  55. package/build/navigation/edit/overlay-panel.js +86 -0
  56. package/build/navigation/edit/overlay-panel.js.map +7 -0
  57. package/build/navigation/edit/overlay-template-part-selector.js +163 -0
  58. package/build/navigation/edit/overlay-template-part-selector.js.map +7 -0
  59. package/build/navigation/edit/overlay-visibility-control.js +54 -0
  60. package/build/navigation/edit/overlay-visibility-control.js.map +7 -0
  61. package/build/navigation/edit/responsive-wrapper.js +14 -2
  62. package/build/navigation/edit/responsive-wrapper.js.map +2 -2
  63. package/build/navigation-link/edit.js +7 -21
  64. package/build/navigation-link/edit.js.map +2 -2
  65. package/build/navigation-link/index.js +1 -1
  66. package/build/navigation-link/index.js.map +2 -2
  67. package/build/navigation-link/shared/index.js +5 -2
  68. package/build/navigation-link/shared/index.js.map +2 -2
  69. package/build/navigation-link/shared/use-handle-link-change.js +84 -0
  70. package/build/navigation-link/shared/use-handle-link-change.js.map +7 -0
  71. package/build/navigation-submenu/index.js +1 -1
  72. package/build/navigation-submenu/index.js.map +2 -2
  73. package/build/page-list-item/edit.js +4 -3
  74. package/build/page-list-item/edit.js.map +2 -2
  75. package/build/paragraph/index.js +1 -1
  76. package/build/paragraph/index.js.map +2 -2
  77. package/build/post-title/edit.js +6 -4
  78. package/build/post-title/edit.js.map +2 -2
  79. package/build/preformatted/index.js +1 -1
  80. package/build/preformatted/index.js.map +2 -2
  81. package/build/pullquote/index.js +1 -1
  82. package/build/pullquote/index.js.map +2 -2
  83. package/build/query/edit/inspector-controls/author-control.js +0 -1
  84. package/build/query/edit/inspector-controls/author-control.js.map +2 -2
  85. package/build/query/edit/inspector-controls/format-controls.js +0 -1
  86. package/build/query/edit/inspector-controls/format-controls.js.map +2 -2
  87. package/build/query/edit/inspector-controls/parent-control.js +1 -2
  88. package/build/query/edit/inspector-controls/parent-control.js.map +2 -2
  89. package/build/query/edit/inspector-controls/taxonomy-controls.js +0 -1
  90. package/build/query/edit/inspector-controls/taxonomy-controls.js.map +2 -2
  91. package/build/search/index.js +1 -1
  92. package/build/search/index.js.map +2 -2
  93. package/build/social-link/index.js +1 -1
  94. package/build/social-link/index.js.map +2 -2
  95. package/build/template-part/edit/index.js +3 -31
  96. package/build/template-part/edit/index.js.map +2 -2
  97. package/build/template-part/edit/utils/get-template-part-icon.js +9 -4
  98. package/build/template-part/edit/utils/get-template-part-icon.js.map +3 -3
  99. package/build/template-part/edit/utils/hooks.js +3 -2
  100. package/build/template-part/edit/utils/hooks.js.map +2 -2
  101. package/build/terms-query/edit/inspector-controls/include-control.js +0 -1
  102. package/build/terms-query/edit/inspector-controls/include-control.js.map +2 -2
  103. package/build/utils/deprecated-text-align-attributes.js +70 -0
  104. package/build/utils/deprecated-text-align-attributes.js.map +7 -0
  105. package/build/utils/migrate-text-align.js +42 -0
  106. package/build/utils/migrate-text-align.js.map +7 -0
  107. package/build/verse/index.js +1 -1
  108. package/build/verse/index.js.map +2 -2
  109. package/build/video/index.js +1 -1
  110. package/build/video/index.js.map +2 -2
  111. package/build-module/audio/index.js +1 -1
  112. package/build-module/audio/index.js.map +2 -2
  113. package/build-module/avatar/edit.js +13 -38
  114. package/build-module/avatar/edit.js.map +2 -2
  115. package/build-module/breadcrumbs/block.json +3 -2
  116. package/build-module/breadcrumbs/edit.js +18 -18
  117. package/build-module/breadcrumbs/edit.js.map +2 -2
  118. package/build-module/button/block.json +2 -3
  119. package/build-module/button/deprecated.js +205 -1
  120. package/build-module/button/deprecated.js.map +2 -2
  121. package/build-module/button/edit.js +13 -25
  122. package/build-module/button/edit.js.map +2 -2
  123. package/build-module/button/index.js +1 -1
  124. package/build-module/button/index.js.map +2 -2
  125. package/build-module/button/save.js +0 -2
  126. package/build-module/button/save.js.map +2 -2
  127. package/build-module/code/index.js +1 -1
  128. package/build-module/code/index.js.map +2 -2
  129. package/build-module/comments-pagination-numbers/block.json +7 -0
  130. package/build-module/cover/index.js +2 -2
  131. package/build-module/cover/index.js.map +2 -2
  132. package/build-module/details/index.js +1 -1
  133. package/build-module/details/index.js.map +2 -2
  134. package/build-module/file/index.js +1 -1
  135. package/build-module/file/index.js.map +2 -2
  136. package/build-module/freeform/block.json +0 -1
  137. package/build-module/gallery/edit.js +1 -1
  138. package/build-module/gallery/edit.js.map +2 -2
  139. package/build-module/gallery/transforms.js +7 -3
  140. package/build-module/gallery/transforms.js.map +2 -2
  141. package/build-module/heading/index.js +1 -1
  142. package/build-module/heading/index.js.map +2 -2
  143. package/build-module/html/modal.js +19 -7
  144. package/build-module/html/modal.js.map +2 -2
  145. package/build-module/image/index.js +1 -1
  146. package/build-module/image/index.js.map +2 -2
  147. package/build-module/index.js +9 -3
  148. package/build-module/index.js.map +2 -2
  149. package/build-module/list-item/index.js +1 -1
  150. package/build-module/list-item/index.js.map +2 -2
  151. package/build-module/math/edit.js +15 -3
  152. package/build-module/math/edit.js.map +2 -2
  153. package/build-module/media-text/index.js +1 -1
  154. package/build-module/media-text/index.js.map +2 -2
  155. package/build-module/more/index.js +1 -1
  156. package/build-module/more/index.js.map +2 -2
  157. package/build-module/navigation/block.json +3 -0
  158. package/build-module/navigation/edit/index.js +58 -88
  159. package/build-module/navigation/edit/index.js.map +2 -2
  160. package/build-module/navigation/edit/overlay-menu-preview-button.js +65 -0
  161. package/build-module/navigation/edit/overlay-menu-preview-button.js.map +7 -0
  162. package/build-module/navigation/edit/overlay-menu-preview-controls.js +64 -0
  163. package/build-module/navigation/edit/overlay-menu-preview-controls.js.map +7 -0
  164. package/build-module/navigation/edit/overlay-panel.js +58 -0
  165. package/build-module/navigation/edit/overlay-panel.js.map +7 -0
  166. package/build-module/navigation/edit/overlay-template-part-selector.js +142 -0
  167. package/build-module/navigation/edit/overlay-template-part-selector.js.map +7 -0
  168. package/build-module/navigation/edit/overlay-visibility-control.js +36 -0
  169. package/build-module/navigation/edit/overlay-visibility-control.js.map +7 -0
  170. package/build-module/navigation/edit/responsive-wrapper.js +14 -2
  171. package/build-module/navigation/edit/responsive-wrapper.js.map +2 -2
  172. package/build-module/navigation-link/edit.js +9 -23
  173. package/build-module/navigation-link/edit.js.map +2 -2
  174. package/build-module/navigation-link/index.js +1 -1
  175. package/build-module/navigation-link/index.js.map +2 -2
  176. package/build-module/navigation-link/shared/index.js +3 -1
  177. package/build-module/navigation-link/shared/index.js.map +2 -2
  178. package/build-module/navigation-link/shared/use-handle-link-change.js +59 -0
  179. package/build-module/navigation-link/shared/use-handle-link-change.js.map +7 -0
  180. package/build-module/navigation-submenu/index.js +1 -1
  181. package/build-module/navigation-submenu/index.js.map +2 -2
  182. package/build-module/page-list-item/edit.js +4 -3
  183. package/build-module/page-list-item/edit.js.map +2 -2
  184. package/build-module/paragraph/index.js +1 -1
  185. package/build-module/paragraph/index.js.map +2 -2
  186. package/build-module/post-title/edit.js +6 -4
  187. package/build-module/post-title/edit.js.map +2 -2
  188. package/build-module/preformatted/index.js +1 -1
  189. package/build-module/preformatted/index.js.map +2 -2
  190. package/build-module/pullquote/index.js +1 -1
  191. package/build-module/pullquote/index.js.map +2 -2
  192. package/build-module/query/edit/inspector-controls/author-control.js +0 -1
  193. package/build-module/query/edit/inspector-controls/author-control.js.map +2 -2
  194. package/build-module/query/edit/inspector-controls/format-controls.js +0 -1
  195. package/build-module/query/edit/inspector-controls/format-controls.js.map +2 -2
  196. package/build-module/query/edit/inspector-controls/parent-control.js +1 -2
  197. package/build-module/query/edit/inspector-controls/parent-control.js.map +2 -2
  198. package/build-module/query/edit/inspector-controls/taxonomy-controls.js +0 -1
  199. package/build-module/query/edit/inspector-controls/taxonomy-controls.js.map +2 -2
  200. package/build-module/search/index.js +1 -1
  201. package/build-module/search/index.js.map +2 -2
  202. package/build-module/social-link/index.js +1 -1
  203. package/build-module/social-link/index.js.map +2 -2
  204. package/build-module/template-part/edit/index.js +3 -31
  205. package/build-module/template-part/edit/index.js.map +2 -2
  206. package/build-module/template-part/edit/utils/get-template-part-icon.js +10 -4
  207. package/build-module/template-part/edit/utils/get-template-part-icon.js.map +2 -2
  208. package/build-module/template-part/edit/utils/hooks.js +3 -2
  209. package/build-module/template-part/edit/utils/hooks.js.map +2 -2
  210. package/build-module/terms-query/edit/inspector-controls/include-control.js +0 -1
  211. package/build-module/terms-query/edit/inspector-controls/include-control.js.map +2 -2
  212. package/build-module/utils/deprecated-text-align-attributes.js +39 -0
  213. package/build-module/utils/deprecated-text-align-attributes.js.map +7 -0
  214. package/build-module/utils/migrate-text-align.js +21 -0
  215. package/build-module/utils/migrate-text-align.js.map +7 -0
  216. package/build-module/verse/index.js +1 -1
  217. package/build-module/verse/index.js.map +2 -2
  218. package/build-module/video/index.js +1 -1
  219. package/build-module/video/index.js.map +2 -2
  220. package/build-style/accordion-heading/style-rtl.css +2 -18
  221. package/build-style/accordion-heading/style.css +2 -18
  222. package/build-style/classic-rtl.css +24 -0
  223. package/build-style/classic.css +24 -0
  224. package/build-style/common-rtl.css +4 -4
  225. package/build-style/common.css +4 -0
  226. package/build-style/editor-rtl.css +5 -0
  227. package/build-style/editor.css +5 -0
  228. package/build-style/navigation/editor-rtl.css +5 -0
  229. package/build-style/navigation/editor.css +5 -0
  230. package/build-style/style-rtl.css +8 -22
  231. package/build-style/style.css +8 -18
  232. package/build-style/verse/style-rtl.css +2 -0
  233. package/build-style/verse/style.css +2 -0
  234. package/package.json +40 -38
  235. package/src/accordion-heading/style.scss +2 -30
  236. package/src/audio/index.js +1 -1
  237. package/src/avatar/edit.js +68 -83
  238. package/src/breadcrumbs/block.json +3 -2
  239. package/src/breadcrumbs/edit.js +18 -18
  240. package/src/breadcrumbs/index.php +38 -17
  241. package/src/button/block.json +2 -3
  242. package/src/button/deprecated.js +214 -0
  243. package/src/button/edit.js +2 -11
  244. package/src/button/index.js +1 -1
  245. package/src/button/save.js +0 -2
  246. package/src/classic.scss +38 -0
  247. package/src/code/index.js +1 -1
  248. package/src/comments-pagination-numbers/block.json +7 -0
  249. package/src/common.scss +4 -0
  250. package/src/cover/index.js +2 -2
  251. package/src/details/index.js +1 -1
  252. package/src/file/index.js +1 -1
  253. package/src/freeform/block.json +0 -1
  254. package/src/gallery/edit.js +4 -1
  255. package/src/gallery/transforms.js +6 -2
  256. package/src/heading/index.js +1 -1
  257. package/src/html/modal.js +39 -20
  258. package/src/image/index.js +1 -1
  259. package/src/index.js +9 -3
  260. package/src/list-item/index.js +1 -1
  261. package/src/math/edit.js +15 -3
  262. package/src/media-text/index.js +1 -1
  263. package/src/more/index.js +1 -1
  264. package/src/navigation/block.json +3 -0
  265. package/src/navigation/edit/index.js +77 -82
  266. package/src/navigation/edit/overlay-menu-preview-button.js +82 -0
  267. package/src/navigation/edit/overlay-menu-preview-controls.js +65 -0
  268. package/src/navigation/edit/overlay-panel.js +78 -0
  269. package/src/navigation/edit/overlay-template-part-selector.js +198 -0
  270. package/src/navigation/edit/overlay-visibility-control.js +40 -0
  271. package/src/navigation/edit/responsive-wrapper.js +16 -1
  272. package/src/navigation/edit/test/overlay-template-part-selector.js +461 -0
  273. package/src/navigation/editor.scss +5 -0
  274. package/src/navigation-link/edit.js +9 -26
  275. package/src/navigation-link/index.js +1 -1
  276. package/src/navigation-link/shared/index.js +1 -0
  277. package/src/navigation-link/shared/test/use-handle-link-change.test.js +804 -0
  278. package/src/navigation-link/shared/use-handle-link-change.js +91 -0
  279. package/src/navigation-submenu/index.js +1 -1
  280. package/src/page-list/index.php +3 -4
  281. package/src/page-list-item/edit.js +4 -3
  282. package/src/paragraph/index.js +1 -1
  283. package/src/post-title/edit.js +8 -4
  284. package/src/post-title/index.php +1 -1
  285. package/src/preformatted/index.js +1 -1
  286. package/src/pullquote/index.js +1 -1
  287. package/src/query/edit/inspector-controls/author-control.js +0 -1
  288. package/src/query/edit/inspector-controls/format-controls.js +0 -1
  289. package/src/query/edit/inspector-controls/parent-control.js +0 -1
  290. package/src/query/edit/inspector-controls/taxonomy-controls.js +0 -1
  291. package/src/search/index.js +1 -1
  292. package/src/social-link/index.js +1 -1
  293. package/src/template-part/edit/index.js +5 -41
  294. package/src/template-part/edit/utils/get-template-part-icon.js +23 -4
  295. package/src/template-part/edit/utils/hooks.js +10 -2
  296. package/src/terms-query/edit/inspector-controls/include-control.js +0 -1
  297. package/src/utils/deprecated-text-align-attributes.js +45 -0
  298. package/src/utils/migrate-text-align.js +22 -0
  299. package/src/verse/index.js +1 -1
  300. package/src/verse/style.scss +4 -0
  301. package/src/video/index.js +1 -1
  302. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,461 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { render, screen } from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
6
+
7
+ /**
8
+ * WordPress dependencies
9
+ */
10
+ import { useEntityRecords } from '@wordpress/core-data';
11
+
12
+ /**
13
+ * Internal dependencies
14
+ */
15
+ import OverlayTemplatePartSelector from '../overlay-template-part-selector';
16
+
17
+ // Mock useEntityRecords
18
+ jest.mock( '@wordpress/core-data', () => {
19
+ const actual = jest.requireActual( '@wordpress/core-data' );
20
+ return {
21
+ ...actual,
22
+ useEntityRecords: jest.fn(),
23
+ };
24
+ } );
25
+
26
+ const mockSetAttributes = jest.fn();
27
+ const mockOnNavigateToEntityRecord = jest.fn();
28
+
29
+ const defaultProps = {
30
+ overlay: undefined,
31
+ setAttributes: mockSetAttributes,
32
+ onNavigateToEntityRecord: mockOnNavigateToEntityRecord,
33
+ };
34
+
35
+ const templatePart1 = {
36
+ id: 1,
37
+ theme: 'twentytwentyfive',
38
+ slug: 'my-overlay',
39
+ title: {
40
+ rendered: 'My Overlay',
41
+ },
42
+ area: 'overlay',
43
+ };
44
+
45
+ const templatePart2 = {
46
+ id: 2,
47
+ theme: 'twentytwentyfive',
48
+ slug: 'another-overlay',
49
+ title: {
50
+ rendered: 'Another Overlay',
51
+ },
52
+ area: 'overlay',
53
+ };
54
+
55
+ const templatePartOtherArea = {
56
+ id: 3,
57
+ theme: 'twentytwentyfive',
58
+ slug: 'header-part',
59
+ title: {
60
+ rendered: 'Header Part',
61
+ },
62
+ area: 'header',
63
+ };
64
+
65
+ const allTemplateParts = [
66
+ templatePart1,
67
+ templatePart2,
68
+ templatePartOtherArea,
69
+ ];
70
+
71
+ describe( 'OverlayTemplatePartSelector', () => {
72
+ beforeEach( () => {
73
+ jest.clearAllMocks();
74
+ useEntityRecords.mockReturnValue( {
75
+ records: [],
76
+ isResolving: false,
77
+ hasResolved: false,
78
+ } );
79
+ } );
80
+
81
+ describe( 'Loading state', () => {
82
+ it( 'should show loading spinner when template parts are resolving', () => {
83
+ useEntityRecords.mockReturnValue( {
84
+ records: null,
85
+ isResolving: true,
86
+ hasResolved: false,
87
+ } );
88
+
89
+ render( <OverlayTemplatePartSelector { ...defaultProps } /> );
90
+
91
+ expect(
92
+ screen.getByText( 'Loading overlays…' )
93
+ ).toBeInTheDocument();
94
+ } );
95
+ } );
96
+
97
+ describe( 'Template part selection', () => {
98
+ it( 'should show selector with "None (default)" option when no template parts are available', () => {
99
+ useEntityRecords.mockReturnValue( {
100
+ records: [],
101
+ isResolving: false,
102
+ hasResolved: true,
103
+ } );
104
+
105
+ render( <OverlayTemplatePartSelector { ...defaultProps } /> );
106
+
107
+ const select = screen.getByRole( 'combobox', {
108
+ name: 'Overlay template',
109
+ } );
110
+ expect( select ).toBeInTheDocument();
111
+ expect( select ).toHaveValue( '' );
112
+
113
+ // Check for "None (default)" option
114
+ expect(
115
+ screen.getByRole( 'option', { name: 'None (default)' } )
116
+ ).toBeInTheDocument();
117
+ } );
118
+
119
+ it( 'should filter template parts by overlay area', () => {
120
+ useEntityRecords.mockReturnValue( {
121
+ records: allTemplateParts,
122
+ isResolving: false,
123
+ hasResolved: true,
124
+ } );
125
+
126
+ render( <OverlayTemplatePartSelector { ...defaultProps } /> );
127
+
128
+ screen.getByRole( 'combobox', {
129
+ name: 'Overlay template',
130
+ } );
131
+
132
+ // Should have None + 2 overlays (not the header one)
133
+ const options = screen.getAllByRole( 'option' );
134
+ expect( options ).toHaveLength( 3 ); // None + 2 overlay parts
135
+
136
+ expect(
137
+ screen.getByRole( 'option', { name: 'My Overlay' } )
138
+ ).toBeInTheDocument();
139
+ expect(
140
+ screen.getByRole( 'option', { name: 'Another Overlay' } )
141
+ ).toBeInTheDocument();
142
+ expect(
143
+ screen.queryByRole( 'option', { name: 'Header Part' } )
144
+ ).not.toBeInTheDocument();
145
+ } );
146
+
147
+ it( 'should display template part slug when title is missing', () => {
148
+ const templatePartNoTitle = {
149
+ ...templatePart1,
150
+ title: null,
151
+ };
152
+
153
+ useEntityRecords.mockReturnValue( {
154
+ records: [ templatePartNoTitle ],
155
+ isResolving: false,
156
+ hasResolved: true,
157
+ } );
158
+
159
+ render( <OverlayTemplatePartSelector { ...defaultProps } /> );
160
+
161
+ expect(
162
+ screen.getByRole( 'option', { name: 'my-overlay' } )
163
+ ).toBeInTheDocument();
164
+ } );
165
+
166
+ it( 'should call setAttributes when a template part is selected', async () => {
167
+ const user = userEvent.setup();
168
+
169
+ useEntityRecords.mockReturnValue( {
170
+ records: [ templatePart1 ],
171
+ isResolving: false,
172
+ hasResolved: true,
173
+ } );
174
+
175
+ render( <OverlayTemplatePartSelector { ...defaultProps } /> );
176
+
177
+ const select = screen.getByRole( 'combobox', {
178
+ name: 'Overlay template',
179
+ } );
180
+
181
+ await user.selectOptions( select, 'twentytwentyfive//my-overlay' );
182
+
183
+ expect( mockSetAttributes ).toHaveBeenCalledWith( {
184
+ overlay: 'twentytwentyfive//my-overlay',
185
+ } );
186
+ } );
187
+
188
+ it( 'should call setAttributes with undefined when "None (default)" is selected', async () => {
189
+ const user = userEvent.setup();
190
+
191
+ useEntityRecords.mockReturnValue( {
192
+ records: [ templatePart1 ],
193
+ isResolving: false,
194
+ hasResolved: true,
195
+ } );
196
+
197
+ render(
198
+ <OverlayTemplatePartSelector
199
+ { ...defaultProps }
200
+ overlay="twentytwentyfive//my-overlay"
201
+ />
202
+ );
203
+
204
+ const select = screen.getByRole( 'combobox', {
205
+ name: 'Overlay template',
206
+ } );
207
+
208
+ await user.selectOptions( select, '' );
209
+
210
+ expect( mockSetAttributes ).toHaveBeenCalledWith( {
211
+ overlay: undefined,
212
+ } );
213
+ } );
214
+
215
+ it( 'should display selected template part', () => {
216
+ useEntityRecords.mockReturnValue( {
217
+ records: [ templatePart1 ],
218
+ isResolving: false,
219
+ hasResolved: true,
220
+ } );
221
+
222
+ render(
223
+ <OverlayTemplatePartSelector
224
+ { ...defaultProps }
225
+ overlay="twentytwentyfive//my-overlay"
226
+ />
227
+ );
228
+
229
+ const select = screen.getByRole( 'combobox', {
230
+ name: 'Overlay template',
231
+ } );
232
+
233
+ expect( select ).toHaveValue( 'twentytwentyfive//my-overlay' );
234
+ } );
235
+ } );
236
+
237
+ describe( 'Edit button', () => {
238
+ it( 'should not render when no template part is selected', () => {
239
+ useEntityRecords.mockReturnValue( {
240
+ records: [ templatePart1 ],
241
+ isResolving: false,
242
+ hasResolved: true,
243
+ } );
244
+
245
+ render( <OverlayTemplatePartSelector { ...defaultProps } /> );
246
+
247
+ const editButton = screen.queryByRole( 'button', {
248
+ name: 'Edit overlay',
249
+ } );
250
+
251
+ expect( editButton ).not.toBeInTheDocument();
252
+ } );
253
+
254
+ it( 'should not render button when template parts are initially loading', () => {
255
+ useEntityRecords.mockReturnValue( {
256
+ records: [ templatePart1 ],
257
+ isResolving: true,
258
+ hasResolved: false,
259
+ } );
260
+
261
+ render(
262
+ <OverlayTemplatePartSelector
263
+ { ...defaultProps }
264
+ overlay="twentytwentyfive//my-overlay"
265
+ />
266
+ );
267
+
268
+ // Component shows spinner when initially loading, button doesn't render
269
+ expect(
270
+ screen.getByText( 'Loading overlays…' )
271
+ ).toBeInTheDocument();
272
+ expect(
273
+ screen.queryByRole( 'button', {
274
+ name: /Edit overlay/,
275
+ } )
276
+ ).not.toBeInTheDocument();
277
+ } );
278
+
279
+ it( 'should be enabled when a valid template part is selected', () => {
280
+ useEntityRecords.mockReturnValue( {
281
+ records: [ templatePart1 ],
282
+ isResolving: false,
283
+ hasResolved: true,
284
+ } );
285
+
286
+ render(
287
+ <OverlayTemplatePartSelector
288
+ { ...defaultProps }
289
+ overlay="twentytwentyfive//my-overlay"
290
+ />
291
+ );
292
+
293
+ const editButton = screen.getByRole( 'button', {
294
+ name: ( accessibleName ) =>
295
+ accessibleName.startsWith( 'Edit overlay' ),
296
+ } );
297
+
298
+ expect( editButton ).toBeEnabled();
299
+ } );
300
+
301
+ it( 'should be disabled when onNavigateToEntityRecord is not available', () => {
302
+ useEntityRecords.mockReturnValue( {
303
+ records: [ templatePart1 ],
304
+ isResolving: false,
305
+ hasResolved: true,
306
+ } );
307
+
308
+ render(
309
+ <OverlayTemplatePartSelector
310
+ { ...defaultProps }
311
+ overlay="twentytwentyfive//my-overlay"
312
+ onNavigateToEntityRecord={ undefined }
313
+ />
314
+ );
315
+
316
+ const editButton = screen.getByRole( 'button', {
317
+ name: ( accessibleName ) =>
318
+ accessibleName.startsWith( 'Edit overlay' ),
319
+ } );
320
+
321
+ // Button uses accessibleWhenDisabled, so it has aria-disabled instead of disabled
322
+ expect( editButton ).toHaveAttribute( 'aria-disabled', 'true' );
323
+ } );
324
+
325
+ it( 'should call onNavigateToEntityRecord when edit button is clicked', async () => {
326
+ const user = userEvent.setup();
327
+
328
+ useEntityRecords.mockReturnValue( {
329
+ records: [ templatePart1 ],
330
+ isResolving: false,
331
+ hasResolved: true,
332
+ } );
333
+
334
+ render(
335
+ <OverlayTemplatePartSelector
336
+ { ...defaultProps }
337
+ overlay="twentytwentyfive//my-overlay"
338
+ />
339
+ );
340
+
341
+ const editButton = screen.getByRole( 'button', {
342
+ name: ( accessibleName ) =>
343
+ accessibleName.startsWith( 'Edit overlay' ),
344
+ } );
345
+
346
+ await user.click( editButton );
347
+
348
+ expect( mockOnNavigateToEntityRecord ).toHaveBeenCalledWith( {
349
+ postId: 'twentytwentyfive//my-overlay',
350
+ postType: 'wp_template_part',
351
+ } );
352
+ } );
353
+
354
+ it( 'should not call onNavigateToEntityRecord when button is disabled', async () => {
355
+ const user = userEvent.setup();
356
+
357
+ useEntityRecords.mockReturnValue( {
358
+ records: [ templatePart1 ],
359
+ isResolving: false,
360
+ hasResolved: true,
361
+ } );
362
+
363
+ render(
364
+ <OverlayTemplatePartSelector
365
+ { ...defaultProps }
366
+ overlay="twentytwentyfive//my-overlay"
367
+ onNavigateToEntityRecord={ undefined }
368
+ />
369
+ );
370
+
371
+ const editButton = screen.getByRole( 'button', {
372
+ name: ( accessibleName ) =>
373
+ accessibleName.startsWith( 'Edit overlay' ),
374
+ } );
375
+
376
+ // Button uses accessibleWhenDisabled, so it has aria-disabled instead of disabled
377
+ expect( editButton ).toHaveAttribute( 'aria-disabled', 'true' );
378
+
379
+ // Even if clicked, the handler checks for onNavigateToEntityRecord and won't call it
380
+ await user.click( editButton );
381
+
382
+ expect( mockOnNavigateToEntityRecord ).not.toHaveBeenCalled();
383
+ } );
384
+ } );
385
+
386
+ describe( 'Help text', () => {
387
+ it( 'should show help text when no overlays are available', () => {
388
+ useEntityRecords.mockReturnValue( {
389
+ records: [],
390
+ isResolving: false,
391
+ hasResolved: true,
392
+ } );
393
+
394
+ render( <OverlayTemplatePartSelector { ...defaultProps } /> );
395
+
396
+ expect(
397
+ screen.getByText( 'No overlays found.' )
398
+ ).toBeInTheDocument();
399
+ } );
400
+
401
+ it( 'should show default help text when overlays are available', () => {
402
+ useEntityRecords.mockReturnValue( {
403
+ records: [ templatePart1 ],
404
+ isResolving: false,
405
+ hasResolved: true,
406
+ } );
407
+
408
+ render( <OverlayTemplatePartSelector { ...defaultProps } /> );
409
+
410
+ expect(
411
+ screen.getByText(
412
+ 'Select an overlay to use for the navigation.'
413
+ )
414
+ ).toBeInTheDocument();
415
+ } );
416
+ } );
417
+
418
+ describe( 'Accessibility', () => {
419
+ it( 'should have proper ARIA labels on edit button', () => {
420
+ useEntityRecords.mockReturnValue( {
421
+ records: [ templatePart1 ],
422
+ isResolving: false,
423
+ hasResolved: true,
424
+ } );
425
+
426
+ render(
427
+ <OverlayTemplatePartSelector
428
+ { ...defaultProps }
429
+ overlay="twentytwentyfive//my-overlay"
430
+ />
431
+ );
432
+
433
+ const editButton = screen.getByRole( 'button', {
434
+ name: ( accessibleName ) =>
435
+ accessibleName.startsWith( 'Edit overlay' ),
436
+ } );
437
+
438
+ expect( editButton ).toHaveAccessibleName();
439
+ } );
440
+
441
+ it( 'should show loading spinner instead of select control when initially loading', () => {
442
+ useEntityRecords.mockReturnValue( {
443
+ records: null,
444
+ isResolving: true,
445
+ hasResolved: false,
446
+ } );
447
+
448
+ render( <OverlayTemplatePartSelector { ...defaultProps } /> );
449
+
450
+ // Should show loading spinner, not the select control
451
+ expect(
452
+ screen.getByText( 'Loading overlays…' )
453
+ ).toBeInTheDocument();
454
+ expect(
455
+ screen.queryByRole( 'combobox', {
456
+ name: 'Overlay template',
457
+ } )
458
+ ).not.toBeInTheDocument();
459
+ } );
460
+ } );
461
+ } );
@@ -649,3 +649,8 @@ body.editor-styles-wrapper .wp-block-navigation__responsive-container.is-menu-op
649
649
  .wp-block-navigation__submenu-accessibility-notice {
650
650
  grid-column: span 2;
651
651
  }
652
+
653
+ .wp-block-navigation__overlay-edit-button {
654
+ margin-top: $grid-unit-10;
655
+ width: fit-content;
656
+ }
@@ -39,9 +39,9 @@ import { getColors } from '../navigation/edit/utils';
39
39
  import {
40
40
  Controls,
41
41
  LinkUI,
42
- updateAttributes,
43
42
  useEntityBinding,
44
43
  MissingEntityHelpText,
44
+ useHandleLinkChange,
45
45
  } from './shared';
46
46
 
47
47
  const DEFAULT_BLOCK = { name: 'core/navigation-link' };
@@ -279,16 +279,17 @@ export default function NavigationLinkEdit( {
279
279
  const { getBlocks } = useSelect( blockEditorStore );
280
280
 
281
281
  // URL binding logic
282
- const {
283
- clearBinding,
284
- createBinding,
285
- hasUrlBinding,
286
- isBoundEntityAvailable,
287
- } = useEntityBinding( {
282
+ const { hasUrlBinding, isBoundEntityAvailable } = useEntityBinding( {
288
283
  clientId,
289
284
  attributes,
290
285
  } );
291
286
 
287
+ const handleLinkChange = useHandleLinkChange( {
288
+ clientId,
289
+ attributes,
290
+ setAttributes,
291
+ } );
292
+
292
293
  const [ isInvalid, isDraft ] = useIsInvalidLink(
293
294
  kind,
294
295
  type,
@@ -642,25 +643,7 @@ export default function NavigationLinkEdit( {
642
643
  } }
643
644
  anchor={ popoverAnchor }
644
645
  onRemove={ removeLink }
645
- onChange={ ( updatedValue ) => {
646
- const {
647
- isEntityLink,
648
- attributes: updatedAttributes,
649
- } = updateAttributes(
650
- updatedValue,
651
- setAttributes,
652
- attributes
653
- );
654
-
655
- // Handle URL binding based on the final computed state
656
- // Only create bindings for entity links (posts, pages, taxonomies)
657
- // Never create bindings for custom links (manual URLs)
658
- if ( isEntityLink ) {
659
- createBinding( updatedAttributes );
660
- } else {
661
- clearBinding();
662
- }
663
- } }
646
+ onChange={ handleLinkChange }
664
647
  />
665
648
  ) }
666
649
  </a>
@@ -93,7 +93,7 @@ export const settings = {
93
93
  transforms,
94
94
  };
95
95
 
96
- if ( window.__experimentalContentOnlyPatternInsertion ) {
96
+ if ( window.__experimentalContentOnlyInspectorFields ) {
97
97
  settings[ fieldsKey ] = [
98
98
  {
99
99
  id: 'label',
@@ -12,3 +12,4 @@ export {
12
12
  buildNavigationLinkEntityBinding,
13
13
  } from './use-entity-binding';
14
14
  export { LinkUI } from '../link-ui';
15
+ export { useHandleLinkChange } from './use-handle-link-change';