@wordpress/block-library 9.40.2-next.v.202602241322.0 → 9.40.2

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 (226) hide show
  1. package/build/comment-reply-link/edit.cjs +2 -1
  2. package/build/comment-reply-link/edit.cjs.map +2 -2
  3. package/build/comments/edit/placeholder.cjs +2 -1
  4. package/build/comments/edit/placeholder.cjs.map +2 -2
  5. package/build/cover/edit/index.cjs +39 -26
  6. package/build/cover/edit/index.cjs.map +2 -2
  7. package/build/cover/embed-video-utils.cjs +8 -6
  8. package/build/cover/embed-video-utils.cjs.map +2 -2
  9. package/build/embed/embed-preview.cjs +2 -1
  10. package/build/embed/embed-preview.cjs.map +2 -2
  11. package/build/html/block.json +2 -1
  12. package/build/html/modal.cjs +147 -230
  13. package/build/html/modal.cjs.map +3 -3
  14. package/build/icon/block.json +3 -12
  15. package/build/image/image.cjs +1 -1
  16. package/build/image/image.cjs.map +2 -2
  17. package/build/latest-comments/deprecated.cjs +33 -0
  18. package/build/latest-comments/deprecated.cjs.map +2 -2
  19. package/build/navigation/edit/index.cjs +123 -119
  20. package/build/navigation/edit/index.cjs.map +3 -3
  21. package/build/navigation/edit/navigation-menu-selector.cjs +2 -1
  22. package/build/navigation/edit/navigation-menu-selector.cjs.map +2 -2
  23. package/build/navigation/view.cjs +9 -2
  24. package/build/navigation/view.cjs.map +2 -2
  25. package/build/navigation-link/edit.cjs +2 -1
  26. package/build/navigation-link/edit.cjs.map +2 -2
  27. package/build/navigation-link/shared/controls.cjs +29 -52
  28. package/build/navigation-link/shared/controls.cjs.map +3 -3
  29. package/build/navigation-link/shared/use-handle-link-change.cjs +19 -3
  30. package/build/navigation-link/shared/use-handle-link-change.cjs.map +3 -3
  31. package/build/navigation-link/shared/use-link-preview.cjs +8 -9
  32. package/build/navigation-link/shared/use-link-preview.cjs.map +2 -2
  33. package/build/navigation-submenu/edit.cjs +8 -22
  34. package/build/navigation-submenu/edit.cjs.map +2 -2
  35. package/build/nextpage/block.json +0 -1
  36. package/build/page-list-item/edit.cjs +6 -3
  37. package/build/page-list-item/edit.cjs.map +2 -2
  38. package/build/paragraph/edit.cjs +1 -1
  39. package/build/paragraph/edit.cjs.map +1 -1
  40. package/build/post-navigation-link/block.json +3 -1
  41. package/build/post-navigation-link/edit.cjs +36 -2
  42. package/build/post-navigation-link/edit.cjs.map +3 -3
  43. package/build/post-navigation-link/index.cjs +0 -2
  44. package/build/post-navigation-link/index.cjs.map +3 -3
  45. package/build/post-title/block.json +3 -1
  46. package/build/post-title/deprecated.cjs +1 -82
  47. package/build/post-title/deprecated.cjs.map +3 -3
  48. package/build/post-title/edit.cjs +36 -10
  49. package/build/post-title/edit.cjs.map +3 -3
  50. package/build/query-title/block.json +3 -1
  51. package/build/query-title/deprecated.cjs +1 -70
  52. package/build/query-title/deprecated.cjs.map +3 -3
  53. package/build/query-title/edit.cjs +35 -17
  54. package/build/query-title/edit.cjs.map +3 -3
  55. package/build/shortcode/block.json +2 -1
  56. package/build/site-tagline/block.json +4 -6
  57. package/build/site-tagline/deprecated.cjs +1 -66
  58. package/build/site-tagline/deprecated.cjs.map +3 -3
  59. package/build/site-tagline/edit.cjs +28 -14
  60. package/build/site-tagline/edit.cjs.map +3 -3
  61. package/build/site-title/block.json +3 -1
  62. package/build/site-title/deprecated.cjs +1 -79
  63. package/build/site-title/deprecated.cjs.map +3 -3
  64. package/build/site-title/edit.cjs +30 -14
  65. package/build/site-title/edit.cjs.map +3 -3
  66. package/build/tabs-menu-item/block.json +26 -1
  67. package/build/tabs-menu-item/controls.cjs +100 -2
  68. package/build/tabs-menu-item/controls.cjs.map +3 -3
  69. package/build/tabs-menu-item/edit.cjs +65 -6
  70. package/build/tabs-menu-item/edit.cjs.map +2 -2
  71. package/build/tabs-menu-item/save.cjs +15 -1
  72. package/build/tabs-menu-item/save.cjs.map +2 -2
  73. package/build-module/comment-reply-link/edit.mjs +3 -2
  74. package/build-module/comment-reply-link/edit.mjs.map +2 -2
  75. package/build-module/comments/edit/placeholder.mjs +3 -2
  76. package/build-module/comments/edit/placeholder.mjs.map +2 -2
  77. package/build-module/cover/edit/index.mjs +47 -29
  78. package/build-module/cover/edit/index.mjs.map +2 -2
  79. package/build-module/cover/embed-video-utils.mjs +7 -5
  80. package/build-module/cover/embed-video-utils.mjs.map +2 -2
  81. package/build-module/embed/embed-preview.mjs +2 -1
  82. package/build-module/embed/embed-preview.mjs.map +2 -2
  83. package/build-module/html/block.json +2 -1
  84. package/build-module/html/modal.mjs +149 -232
  85. package/build-module/html/modal.mjs.map +2 -2
  86. package/build-module/icon/block.json +3 -12
  87. package/build-module/image/image.mjs +1 -1
  88. package/build-module/image/image.mjs.map +2 -2
  89. package/build-module/latest-comments/deprecated.mjs +33 -0
  90. package/build-module/latest-comments/deprecated.mjs.map +2 -2
  91. package/build-module/navigation/edit/index.mjs +123 -119
  92. package/build-module/navigation/edit/index.mjs.map +2 -2
  93. package/build-module/navigation/edit/navigation-menu-selector.mjs +2 -1
  94. package/build-module/navigation/edit/navigation-menu-selector.mjs.map +2 -2
  95. package/build-module/navigation/view.mjs +9 -2
  96. package/build-module/navigation/view.mjs.map +2 -2
  97. package/build-module/navigation-link/edit.mjs +2 -1
  98. package/build-module/navigation-link/edit.mjs.map +2 -2
  99. package/build-module/navigation-link/shared/controls.mjs +29 -53
  100. package/build-module/navigation-link/shared/controls.mjs.map +2 -2
  101. package/build-module/navigation-link/shared/use-handle-link-change.mjs +19 -3
  102. package/build-module/navigation-link/shared/use-handle-link-change.mjs.map +2 -2
  103. package/build-module/navigation-link/shared/use-link-preview.mjs +8 -9
  104. package/build-module/navigation-link/shared/use-link-preview.mjs.map +2 -2
  105. package/build-module/navigation-submenu/edit.mjs +9 -23
  106. package/build-module/navigation-submenu/edit.mjs.map +2 -2
  107. package/build-module/nextpage/block.json +0 -1
  108. package/build-module/page-list-item/edit.mjs +6 -3
  109. package/build-module/page-list-item/edit.mjs.map +2 -2
  110. package/build-module/paragraph/edit.mjs +2 -2
  111. package/build-module/paragraph/edit.mjs.map +1 -1
  112. package/build-module/post-navigation-link/block.json +3 -1
  113. package/build-module/post-navigation-link/edit.mjs +30 -3
  114. package/build-module/post-navigation-link/edit.mjs.map +2 -2
  115. package/build-module/post-navigation-link/index.mjs +0 -2
  116. package/build-module/post-navigation-link/index.mjs.map +2 -2
  117. package/build-module/post-title/block.json +3 -1
  118. package/build-module/post-title/deprecated.mjs +1 -82
  119. package/build-module/post-title/deprecated.mjs.map +2 -2
  120. package/build-module/post-title/edit.mjs +27 -10
  121. package/build-module/post-title/edit.mjs.map +2 -2
  122. package/build-module/query-title/block.json +3 -1
  123. package/build-module/query-title/deprecated.mjs +1 -70
  124. package/build-module/query-title/deprecated.mjs.map +2 -2
  125. package/build-module/query-title/edit.mjs +36 -17
  126. package/build-module/query-title/edit.mjs.map +2 -2
  127. package/build-module/shortcode/block.json +2 -1
  128. package/build-module/site-tagline/block.json +4 -6
  129. package/build-module/site-tagline/deprecated.mjs +1 -66
  130. package/build-module/site-tagline/deprecated.mjs.map +2 -2
  131. package/build-module/site-tagline/edit.mjs +29 -14
  132. package/build-module/site-tagline/edit.mjs.map +2 -2
  133. package/build-module/site-title/block.json +3 -1
  134. package/build-module/site-title/deprecated.mjs +1 -79
  135. package/build-module/site-title/deprecated.mjs.map +2 -2
  136. package/build-module/site-title/edit.mjs +31 -14
  137. package/build-module/site-title/edit.mjs.map +2 -2
  138. package/build-module/tabs-menu-item/block.json +26 -1
  139. package/build-module/tabs-menu-item/controls.mjs +104 -3
  140. package/build-module/tabs-menu-item/controls.mjs.map +2 -2
  141. package/build-module/tabs-menu-item/edit.mjs +66 -6
  142. package/build-module/tabs-menu-item/edit.mjs.map +2 -2
  143. package/build-module/tabs-menu-item/save.mjs +15 -1
  144. package/build-module/tabs-menu-item/save.mjs.map +2 -2
  145. package/build-style/editor-rtl.css +30 -6
  146. package/build-style/editor.css +30 -6
  147. package/build-style/html/editor-rtl.css +10 -6
  148. package/build-style/html/editor.css +10 -6
  149. package/build-style/icon/editor-rtl.css +5 -0
  150. package/build-style/icon/editor.css +5 -0
  151. package/build-style/navigation/style-rtl.css +15 -1
  152. package/build-style/navigation/style.css +15 -1
  153. package/build-style/navigation-link/editor-rtl.css +10 -0
  154. package/build-style/navigation-link/editor.css +10 -0
  155. package/build-style/navigation-overlay-close/style-rtl.css +3 -3
  156. package/build-style/navigation-overlay-close/style.css +3 -3
  157. package/build-style/style-rtl.css +30 -7
  158. package/build-style/style.css +30 -7
  159. package/build-style/tabs-menu-item/editor-rtl.css +5 -0
  160. package/build-style/tabs-menu-item/editor.css +5 -0
  161. package/build-style/tabs-menu-item/style-rtl.css +12 -3
  162. package/build-style/tabs-menu-item/style.css +12 -3
  163. package/package.json +38 -38
  164. package/src/accordion-item/index.php +17 -5
  165. package/src/comment-reply-link/edit.js +5 -2
  166. package/src/comments/edit/placeholder.js +5 -2
  167. package/src/cover/edit/index.js +72 -35
  168. package/src/cover/embed-video-utils.js +9 -7
  169. package/src/cover/index.php +8 -0
  170. package/src/details/index.php +47 -0
  171. package/src/embed/embed-preview.js +6 -5
  172. package/src/html/block.json +2 -1
  173. package/src/html/editor.scss +15 -5
  174. package/src/html/modal.js +32 -99
  175. package/src/icon/block.json +3 -12
  176. package/src/icon/editor.scss +5 -0
  177. package/src/icon/index.php +3 -1
  178. package/src/image/image.js +14 -15
  179. package/src/image/index.php +7 -5
  180. package/src/latest-comments/deprecated.js +33 -0
  181. package/src/navigation/edit/index.js +134 -130
  182. package/src/navigation/edit/navigation-menu-selector.js +2 -1
  183. package/src/navigation/index.php +62 -9
  184. package/src/navigation/style.scss +17 -1
  185. package/src/navigation/view.js +14 -2
  186. package/src/navigation-link/edit.js +1 -0
  187. package/src/navigation-link/editor.scss +18 -0
  188. package/src/navigation-link/shared/controls.js +35 -62
  189. package/src/navigation-link/shared/test/controls.js +5 -5
  190. package/src/navigation-link/shared/test/use-handle-link-change.test.js +212 -0
  191. package/src/navigation-link/shared/test/use-link-preview.test.js +19 -1
  192. package/src/navigation-link/shared/use-handle-link-change.js +36 -9
  193. package/src/navigation-link/shared/use-link-preview.js +14 -15
  194. package/src/navigation-overlay-close/style.scss +3 -3
  195. package/src/navigation-submenu/edit.js +11 -28
  196. package/src/nextpage/block.json +0 -1
  197. package/src/page-list-item/edit.js +8 -7
  198. package/src/paragraph/edit.js +2 -2
  199. package/src/post-featured-image/index.php +4 -2
  200. package/src/post-navigation-link/block.json +3 -1
  201. package/src/post-navigation-link/edit.js +35 -2
  202. package/src/post-navigation-link/index.js +0 -2
  203. package/src/post-title/block.json +3 -1
  204. package/src/post-title/deprecated.js +1 -86
  205. package/src/post-title/edit.js +18 -2
  206. package/src/query-title/block.json +3 -1
  207. package/src/query-title/deprecated.js +1 -74
  208. package/src/query-title/edit.js +27 -11
  209. package/src/shortcode/block.json +2 -1
  210. package/src/site-tagline/block.json +4 -6
  211. package/src/site-tagline/deprecated.js +1 -70
  212. package/src/site-tagline/edit.js +22 -9
  213. package/src/site-title/block.json +3 -1
  214. package/src/site-title/deprecated.js +1 -83
  215. package/src/site-title/edit.js +22 -9
  216. package/src/tabs-menu-item/block.json +26 -1
  217. package/src/tabs-menu-item/controls.js +108 -0
  218. package/src/tabs-menu-item/edit.js +79 -6
  219. package/src/tabs-menu-item/editor.scss +6 -0
  220. package/src/tabs-menu-item/save.js +26 -1
  221. package/src/tabs-menu-item/style.scss +14 -3
  222. package/build/post-navigation-link/deprecated.cjs +0 -100
  223. package/build/post-navigation-link/deprecated.cjs.map +0 -7
  224. package/build-module/post-navigation-link/deprecated.mjs +0 -69
  225. package/build-module/post-navigation-link/deprecated.mjs.map +0 -7
  226. package/src/post-navigation-link/deprecated.js +0 -72
@@ -237,7 +237,7 @@ describe( 'Controls', () => {
237
237
 
238
238
  render( <Controls { ...propsWithExternalLink } /> );
239
239
 
240
- expect( screen.getByText( 'View link' ) ).toBeVisible();
240
+ expect( screen.getByText( 'View' ) ).toBeVisible();
241
241
  } );
242
242
 
243
243
  it( 'shows "View page" for page links', () => {
@@ -253,7 +253,7 @@ describe( 'Controls', () => {
253
253
 
254
254
  render( <Controls { ...propsWithPageLink } /> );
255
255
 
256
- expect( screen.getByText( 'View page' ) ).toBeVisible();
256
+ expect( screen.getByText( 'View' ) ).toBeVisible();
257
257
  } );
258
258
 
259
259
  it( 'shows "View post" for post links', () => {
@@ -269,7 +269,7 @@ describe( 'Controls', () => {
269
269
 
270
270
  render( <Controls { ...propsWithPostLink } /> );
271
271
 
272
- expect( screen.getByText( 'View post' ) ).toBeVisible();
272
+ expect( screen.getByText( 'View' ) ).toBeVisible();
273
273
  } );
274
274
 
275
275
  it( 'shows "View category" for category links', () => {
@@ -285,7 +285,7 @@ describe( 'Controls', () => {
285
285
 
286
286
  render( <Controls { ...propsWithCategoryLink } /> );
287
287
 
288
- expect( screen.getByText( 'View category' ) ).toBeVisible();
288
+ expect( screen.getByText( 'View' ) ).toBeVisible();
289
289
  } );
290
290
 
291
291
  it( 'shows "View link" for custom type links', () => {
@@ -301,7 +301,7 @@ describe( 'Controls', () => {
301
301
 
302
302
  render( <Controls { ...propsWithCustomLink } /> );
303
303
 
304
- expect( screen.getByText( 'View link' ) ).toBeVisible();
304
+ expect( screen.getByText( 'View' ) ).toBeVisible();
305
305
  } );
306
306
  } );
307
307
  } );
@@ -423,6 +423,51 @@ describe( 'useHandleLinkChange', () => {
423
423
  } )
424
424
  );
425
425
  } );
426
+
427
+ it( 'should update text when editing text and changing a bound entity link to a custom URL in the link editing UI', () => {
428
+ useEntityBinding.mockReturnValue( {
429
+ hasUrlBinding: true,
430
+ createBinding: mockCreateBinding,
431
+ clearBinding: mockClearBinding,
432
+ } );
433
+
434
+ const attributes = {
435
+ id: 456,
436
+ url: 'https://example.com/my-page',
437
+ label: 'My Page',
438
+ kind: 'post-type',
439
+ type: 'page',
440
+ };
441
+
442
+ const { result } = renderHook( () =>
443
+ useHandleLinkChange( {
444
+ clientId,
445
+ attributes,
446
+ setAttributes: mockSetAttributes,
447
+ allowTextUpdate: true,
448
+ } )
449
+ );
450
+
451
+ const updatedLink = {
452
+ url: 'https://external-site.com',
453
+ title: 'Updated Navigation Text',
454
+ };
455
+
456
+ result.current( updatedLink );
457
+
458
+ expect( mockClearBinding ).toHaveBeenCalled();
459
+ expect( mockUpdateBlockAttributes ).toHaveBeenCalledWith(
460
+ clientId,
461
+ expect.objectContaining( {
462
+ url: 'https://external-site.com',
463
+ kind: 'custom',
464
+ type: 'custom',
465
+ id: undefined,
466
+ label: 'Updated Navigation Text',
467
+ } )
468
+ );
469
+ expect( updateAttributes ).not.toHaveBeenCalled();
470
+ } );
426
471
  } );
427
472
 
428
473
  describe( 'updating existing links', () => {
@@ -503,6 +548,87 @@ describe( 'useHandleLinkChange', () => {
503
548
  );
504
549
  } );
505
550
 
551
+ it( 'should preserve label when changing the link without editing text in the link editing UI', () => {
552
+ const attributes = {
553
+ id: 123,
554
+ url: 'https://example.com/page',
555
+ label: 'Custom Label',
556
+ kind: 'post-type',
557
+ type: 'page',
558
+ };
559
+
560
+ const { result } = renderHook( () =>
561
+ useHandleLinkChange( {
562
+ clientId,
563
+ attributes,
564
+ setAttributes: mockSetAttributes,
565
+ allowTextUpdate: true,
566
+ } )
567
+ );
568
+
569
+ const updatedLink = {
570
+ id: 456,
571
+ url: 'https://example.com/new-page',
572
+ title: 'Custom Label',
573
+ kind: 'post-type',
574
+ type: 'page',
575
+ };
576
+
577
+ result.current( updatedLink );
578
+
579
+ expect( updateAttributes ).toHaveBeenCalledWith(
580
+ expect.not.objectContaining( {
581
+ title: 'Custom Label',
582
+ } ),
583
+ mockSetAttributes,
584
+ attributes
585
+ );
586
+ expect( mockUpdateBlockAttributes ).not.toHaveBeenCalled();
587
+ } );
588
+
589
+ it( 'should include title when editing text and changing the entity link in the link editing UI', () => {
590
+ const attributes = {
591
+ id: 123,
592
+ url: 'https://example.com/page',
593
+ label: 'Sample Page',
594
+ kind: 'post-type',
595
+ type: 'page',
596
+ };
597
+
598
+ const { result } = renderHook( () =>
599
+ useHandleLinkChange( {
600
+ clientId,
601
+ attributes,
602
+ setAttributes: mockSetAttributes,
603
+ allowTextUpdate: true,
604
+ } )
605
+ );
606
+
607
+ const updatedLink = {
608
+ id: 456,
609
+ url: 'https://example.com/new-page',
610
+ title: 'Updated Navigation Text',
611
+ kind: 'post-type',
612
+ type: 'page',
613
+ };
614
+
615
+ result.current( updatedLink );
616
+
617
+ expect( updateAttributes ).toHaveBeenCalledWith(
618
+ expect.objectContaining( {
619
+ title: 'Updated Navigation Text',
620
+ } ),
621
+ mockSetAttributes,
622
+ attributes
623
+ );
624
+ expect( mockUpdateBlockAttributes ).toHaveBeenCalledWith(
625
+ clientId,
626
+ {
627
+ label: 'Updated Navigation Text',
628
+ }
629
+ );
630
+ } );
631
+
506
632
  it( 'should include title when creating link without existing label', () => {
507
633
  const attributes = {};
508
634
 
@@ -606,6 +732,92 @@ describe( 'useHandleLinkChange', () => {
606
732
  );
607
733
  } );
608
734
 
735
+ it( 'should include title when editing text for the same existing entity link', () => {
736
+ const attributes = {
737
+ id: 123,
738
+ url: 'https://example.com/page',
739
+ label: 'Sample Page',
740
+ kind: 'post-type',
741
+ type: 'page',
742
+ };
743
+
744
+ const { result } = renderHook( () =>
745
+ useHandleLinkChange( {
746
+ clientId,
747
+ attributes,
748
+ setAttributes: mockSetAttributes,
749
+ allowTextUpdate: true,
750
+ } )
751
+ );
752
+
753
+ const updatedLink = {
754
+ id: 123,
755
+ url: 'https://example.com/page',
756
+ title: 'Updated Sample Page',
757
+ kind: 'post-type',
758
+ type: 'page',
759
+ };
760
+
761
+ result.current( updatedLink );
762
+
763
+ expect( updateAttributes ).toHaveBeenCalledWith(
764
+ expect.objectContaining( {
765
+ title: 'Updated Sample Page',
766
+ } ),
767
+ mockSetAttributes,
768
+ attributes
769
+ );
770
+ expect( mockUpdateBlockAttributes ).toHaveBeenCalledWith(
771
+ clientId,
772
+ {
773
+ label: 'Updated Sample Page',
774
+ }
775
+ );
776
+ } );
777
+
778
+ it( 'should clear text when the inline link editing UI title is emptied', () => {
779
+ const attributes = {
780
+ id: 123,
781
+ url: 'https://example.com/page',
782
+ label: 'Sample Page',
783
+ kind: 'post-type',
784
+ type: 'page',
785
+ };
786
+
787
+ const { result } = renderHook( () =>
788
+ useHandleLinkChange( {
789
+ clientId,
790
+ attributes,
791
+ setAttributes: mockSetAttributes,
792
+ allowTextUpdate: true,
793
+ } )
794
+ );
795
+
796
+ const updatedLink = {
797
+ id: 123,
798
+ url: 'https://example.com/page',
799
+ title: '',
800
+ kind: 'post-type',
801
+ type: 'page',
802
+ };
803
+
804
+ result.current( updatedLink );
805
+
806
+ expect( updateAttributes ).toHaveBeenCalledWith(
807
+ expect.objectContaining( {
808
+ title: '',
809
+ } ),
810
+ mockSetAttributes,
811
+ attributes
812
+ );
813
+ expect( mockUpdateBlockAttributes ).toHaveBeenCalledWith(
814
+ clientId,
815
+ {
816
+ label: '',
817
+ }
818
+ );
819
+ } );
820
+
609
821
  it( 'should update custom link to another custom link', () => {
610
822
  updateAttributes.mockImplementation( ( attrs ) => ( {
611
823
  isEntityLink: false,
@@ -92,10 +92,28 @@ describe( 'computeDisplayUrl', () => {
92
92
  it( 'should treat same-origin URLs as internal', () => {
93
93
  const result = computeDisplayUrl( {
94
94
  linkUrl: 'https://example.com/my-page',
95
- siteUrl: 'https://example.com',
95
+ homeUrl: 'https://example.com',
96
96
  } );
97
97
  expect( result.isExternal ).toBe( false );
98
98
  } );
99
+
100
+ it( 'should treat same-origin URLs as internal when homeUrl includes a path', () => {
101
+ const result = computeDisplayUrl( {
102
+ linkUrl: 'https://example.com/my-page',
103
+ homeUrl: 'https://example.com/blog',
104
+ } );
105
+ expect( result.isExternal ).toBe( false );
106
+ expect( result.displayUrl ).toBe( '/my-page' );
107
+ } );
108
+
109
+ it( 'should treat http and https to same host as internal (compare by host, not origin)', () => {
110
+ const result = computeDisplayUrl( {
111
+ linkUrl: 'http://example.com/my-page',
112
+ homeUrl: 'https://example.com',
113
+ } );
114
+ expect( result.isExternal ).toBe( false );
115
+ expect( result.displayUrl ).toBe( '/my-page' );
116
+ } );
99
117
  } );
100
118
 
101
119
  describe( 'special protocols and edge cases', () => {
@@ -4,6 +4,8 @@
4
4
  import { useCallback } from '@wordpress/element';
5
5
  import { useDispatch } from '@wordpress/data';
6
6
  import { store as blockEditorStore } from '@wordpress/block-editor';
7
+ import { __unstableStripHTML as stripHTML } from '@wordpress/dom';
8
+ import { escapeHTML } from '@wordpress/escape-html';
7
9
 
8
10
  /**
9
11
  * Internal dependencies
@@ -16,13 +18,19 @@ import { useEntityBinding } from './use-entity-binding';
16
18
  * Manages the transition between entity links and custom links,
17
19
  * including proper binding creation and cleanup.
18
20
  *
19
- * @param {Object} options - Configuration options
20
- * @param {string} options.clientId - Block client ID
21
- * @param {Object} options.attributes - Current block attributes
22
- * @param {Function} options.setAttributes - Standard setAttribute function
21
+ * @param {Object} options - Configuration options
22
+ * @param {string} options.clientId - Block client ID
23
+ * @param {Object} options.attributes - Current block attributes
24
+ * @param {Function} options.setAttributes - Standard setAttribute function
25
+ * @param {boolean} options.allowTextUpdate - Whether this control can update the link text
23
26
  * @return {Function} Callback function to handle link changes
24
27
  */
25
- export function useHandleLinkChange( { clientId, attributes, setAttributes } ) {
28
+ export function useHandleLinkChange( {
29
+ clientId,
30
+ attributes,
31
+ setAttributes,
32
+ allowTextUpdate = false,
33
+ } ) {
26
34
  const { updateBlockAttributes } = useDispatch( blockEditorStore );
27
35
  const { hasUrlBinding, createBinding, clearBinding } = useEntityBinding( {
28
36
  clientId,
@@ -42,12 +50,25 @@ export function useHandleLinkChange( { clientId, attributes, setAttributes } ) {
42
50
  id: updatedLink.id,
43
51
  };
44
52
 
45
- // Only include title when there's no existing label
46
- // This preserves user-customized labels when updating links
47
- if ( ! attributes.label || attributes.label === '' ) {
53
+ const currentText = attributes.label
54
+ ? stripHTML( attributes.label )
55
+ : '';
56
+ const updatedText = updatedLink.title ?? '';
57
+ const hasTextUpdate =
58
+ allowTextUpdate &&
59
+ updatedLink.title !== undefined &&
60
+ updatedText !== currentText;
61
+ const textUpdateAttributes = hasTextUpdate
62
+ ? { label: escapeHTML( updatedText ) }
63
+ : {};
64
+
65
+ if (
66
+ ! attributes.label ||
67
+ attributes.label === '' ||
68
+ hasTextUpdate
69
+ ) {
48
70
  attrs.title = updatedLink.title;
49
71
  }
50
-
51
72
  // Check if transitioning from entity to custom link
52
73
  const willBeCustomLink = ! updatedLink.id && hasUrlBinding;
53
74
 
@@ -62,6 +83,7 @@ export function useHandleLinkChange( { clientId, attributes, setAttributes } ) {
62
83
  kind: 'custom',
63
84
  type: 'custom',
64
85
  id: undefined,
86
+ ...textUpdateAttributes,
65
87
  } );
66
88
  } else {
67
89
  // Normal flow for entity links or unbound custom links
@@ -76,10 +98,15 @@ export function useHandleLinkChange( { clientId, attributes, setAttributes } ) {
76
98
  } else {
77
99
  clearBinding();
78
100
  }
101
+
102
+ if ( Object.keys( textUpdateAttributes ).length ) {
103
+ updateBlockAttributes( clientId, textUpdateAttributes );
104
+ }
79
105
  }
80
106
  },
81
107
  [
82
108
  attributes,
109
+ allowTextUpdate,
83
110
  clientId,
84
111
  hasUrlBinding,
85
112
  createBinding,
@@ -31,10 +31,10 @@ function capitalize( str ) {
31
31
  *
32
32
  * @param {Object} options - Parameters object
33
33
  * @param {string} options.linkUrl - The URL to process
34
- * @param {string} options.siteUrl - The WordPress site URL (falls back to window.location.origin)
34
+ * @param {string} options.homeUrl - The WordPress site URL (required for internal/external detection)
35
35
  * @return {Object} Object with displayUrl and isExternal flag
36
36
  */
37
- export function computeDisplayUrl( { linkUrl, siteUrl } = {} ) {
37
+ export function computeDisplayUrl( { linkUrl, homeUrl } = {} ) {
38
38
  if ( ! linkUrl ) {
39
39
  return { displayUrl: '', isExternal: false };
40
40
  }
@@ -51,9 +51,10 @@ export function computeDisplayUrl( { linkUrl, siteUrl } = {} ) {
51
51
  // This must happen before trusting the type attribute
52
52
  try {
53
53
  const parsedUrl = new URL( linkUrl );
54
- // Use provided siteUrl or fall back to window.location.origin
55
- const siteDomain = siteUrl || window.location.origin;
56
- if ( parsedUrl.origin === siteDomain ) {
54
+ // Compare by host (not origin) so http/https to same site both count as internal
55
+ const siteHost = new URL( homeUrl ).host;
56
+
57
+ if ( parsedUrl.host === siteHost ) {
57
58
  // Show only the pathname (and search/hash if present)
58
59
  let path = parsedUrl.pathname + parsedUrl.search + parsedUrl.hash;
59
60
  // Remove trailing slash
@@ -62,12 +63,11 @@ export function computeDisplayUrl( { linkUrl, siteUrl } = {} ) {
62
63
  }
63
64
  displayUrl = path;
64
65
  } else {
65
- // Different origin - this is an external link
66
+ // Different host - this is an external link
66
67
  isExternal = true;
67
68
  }
68
69
  } catch ( e ) {
69
- // URL parsing failed - this means it's likely a URL without a protocol (e.g., "www.example.com")
70
- // Since we already checked for relative paths and hash links above, treat as external
70
+ // URL parsing failed - treat as external (e.g. no homeUrl, or URL without protocol)
71
71
  isExternal = true;
72
72
  }
73
73
 
@@ -174,13 +174,12 @@ export function useLinkPreview( {
174
174
  hasBinding,
175
175
  isEntityAvailable,
176
176
  } ) {
177
- // Get the WordPress site URL from settings
178
- const siteUrl = useSelect( ( select ) => {
179
- const siteEntity = select( coreDataStore ).getEntityRecord(
177
+ // Get the WordPress homepage URL from settings
178
+ const homeUrl = useSelect( ( select ) => {
179
+ return select( coreDataStore ).getEntityRecord(
180
180
  'root',
181
- 'site'
182
- );
183
- return siteEntity?.url;
181
+ '__unstableBase'
182
+ )?.home;
184
183
  }, [] );
185
184
 
186
185
  const title =
@@ -194,7 +193,7 @@ export function useLinkPreview( {
194
193
  // Compute display URL and external flag
195
194
  const { displayUrl, isExternal } = computeDisplayUrl( {
196
195
  linkUrl: url,
197
- siteUrl,
196
+ homeUrl,
198
197
  } );
199
198
 
200
199
  const image = useSelect(
@@ -3,7 +3,7 @@
3
3
  align-items: center;
4
4
  justify-content: center;
5
5
  gap: 0.5em;
6
- padding: 0.5em;
6
+ padding: 0;
7
7
  border: none;
8
8
  background: transparent;
9
9
  cursor: pointer;
@@ -14,8 +14,8 @@
14
14
  }
15
15
 
16
16
  svg {
17
- width: 1.5em;
18
- height: 1.5em;
17
+ width: 24px;
18
+ height: 24px;
19
19
  fill: currentColor;
20
20
  display: block;
21
21
  flex-shrink: 0;
@@ -35,8 +35,8 @@ import { ItemSubmenuIcon } from './icons';
35
35
  import {
36
36
  Controls,
37
37
  LinkUI,
38
- updateAttributes,
39
38
  useEntityBinding,
39
+ useHandleLinkChange,
40
40
  useIsInvalidLink,
41
41
  InvalidDraftDisplay,
42
42
  useEnableLinkStatusValidation,
@@ -92,15 +92,17 @@ export default function NavigationSubmenuEdit( {
92
92
  blockEditingMode !== 'default' ? true : submenuVisibility === 'click';
93
93
 
94
94
  // URL binding logic
95
- const {
96
- clearBinding,
97
- createBinding,
98
- hasUrlBinding,
99
- isBoundEntityAvailable,
100
- entityRecord,
101
- } = useEntityBinding( {
95
+ const { hasUrlBinding, isBoundEntityAvailable, entityRecord } =
96
+ useEntityBinding( {
97
+ clientId,
98
+ attributes,
99
+ } );
100
+
101
+ const handleLinkChange = useHandleLinkChange( {
102
102
  clientId,
103
103
  attributes,
104
+ setAttributes,
105
+ allowTextUpdate: true,
104
106
  } );
105
107
 
106
108
  const { __unstableMarkNextChangeAsNotPersistent, replaceBlock } =
@@ -398,26 +400,7 @@ export default function NavigationSubmenuEdit( {
398
400
  setAttributes( { url: '' } );
399
401
  speak( __( 'Link removed.' ), 'assertive' );
400
402
  } }
401
- onChange={ ( updatedValue ) => {
402
- // updateAttributes determines the final state and returns metadata
403
- const {
404
- isEntityLink,
405
- attributes: updatedAttributes,
406
- } = updateAttributes(
407
- updatedValue,
408
- setAttributes,
409
- attributes
410
- );
411
-
412
- // Handle URL binding based on the final computed state
413
- // Only create bindings for entity links (posts, pages, taxonomies)
414
- // Never create bindings for custom links (manual URLs)
415
- if ( isEntityLink ) {
416
- createBinding( updatedAttributes );
417
- } else {
418
- clearBinding();
419
- }
420
- } }
403
+ onChange={ handleLinkChange }
421
404
  />
422
405
  ) }
423
406
  </ParentElement>
@@ -9,7 +9,6 @@
9
9
  "parent": [ "core/post-content" ],
10
10
  "textdomain": "default",
11
11
  "supports": {
12
- "anchor": true,
13
12
  "customClassName": false,
14
13
  "className": false,
15
14
  "html": false,
@@ -8,7 +8,6 @@ import clsx from 'clsx';
8
8
  import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';
9
9
  import { useSelect } from '@wordpress/data';
10
10
  import { store as coreStore } from '@wordpress/core-data';
11
- import { RawHTML } from '@wordpress/element';
12
11
  import { safeHTML } from '@wordpress/dom';
13
12
 
14
13
  /**
@@ -78,9 +77,10 @@ export default function PageListItemEdit( { context, attributes } ) {
78
77
  type="button"
79
78
  className="wp-block-navigation-item__content wp-block-navigation-submenu__toggle"
80
79
  aria-expanded="false"
81
- >
82
- <RawHTML>{ safeHTML( label ) }</RawHTML>
83
- </button>
80
+ dangerouslySetInnerHTML={ {
81
+ __html: safeHTML( label ),
82
+ } }
83
+ />
84
84
  <span className="wp-block-page-list__submenu-icon wp-block-navigation__submenu-icon">
85
85
  <ItemSubmenuIcon />
86
86
  </span>
@@ -91,9 +91,10 @@ export default function PageListItemEdit( { context, attributes } ) {
91
91
  'wp-block-navigation-item__content': isNavigationChild,
92
92
  } ) }
93
93
  href={ link }
94
- >
95
- <RawHTML>{ safeHTML( title ) }</RawHTML>
96
- </a>
94
+ dangerouslySetInnerHTML={ {
95
+ __html: safeHTML( title ),
96
+ } }
97
+ />
97
98
  ) }
98
99
  { hasChildren && (
99
100
  <>
@@ -21,7 +21,7 @@ import {
21
21
  useBlockEditingMode,
22
22
  } from '@wordpress/block-editor';
23
23
  import { getBlockSupport } from '@wordpress/blocks';
24
- import { formatLtr } from '@wordpress/icons';
24
+ import { formatLTR } from '@wordpress/icons';
25
25
  /**
26
26
  * Internal dependencies
27
27
  */
@@ -32,7 +32,7 @@ function ParagraphRTLControl( { direction, setDirection } ) {
32
32
  return (
33
33
  isRTL() && (
34
34
  <ToolbarButton
35
- icon={ formatLtr }
35
+ icon={ formatLTR }
36
36
  title={ _x( 'Left to right', 'editor button' ) }
37
37
  isActive={ direction === 'ltr' }
38
38
  onClick={ () => {
@@ -166,8 +166,10 @@ function get_block_core_post_featured_image_overlay_element_markup( $attributes
166
166
  }
167
167
 
168
168
  // Apply overlay and gradient classes.
169
- $class_names[] = 'has-background-dim';
170
- $class_names[] = "has-background-dim-{$attributes['dimRatio']}";
169
+ if ( $has_dim_background ) {
170
+ $class_names[] = 'has-background-dim';
171
+ $class_names[] = "has-background-dim-{$attributes['dimRatio']}";
172
+ }
171
173
 
172
174
  if ( $has_solid_overlay ) {
173
175
  $class_names[] = "has-{$attributes['overlayColor']}-background-color";
@@ -7,6 +7,9 @@
7
7
  "description": "Displays the next or previous post link that is adjacent to the current post.",
8
8
  "textdomain": "default",
9
9
  "attributes": {
10
+ "textAlign": {
11
+ "type": "string"
12
+ },
10
13
  "type": {
11
14
  "type": "string",
12
15
  "default": "next"
@@ -43,7 +46,6 @@
43
46
  "typography": {
44
47
  "fontSize": true,
45
48
  "lineHeight": true,
46
- "textAlign": true,
47
49
  "__experimentalFontFamily": true,
48
50
  "__experimentalFontWeight": true,
49
51
  "__experimentalFontStyle": true,