@wordpress/block-library 9.45.1-next.v.202605131032.0 → 9.47.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 (182) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/build/button/edit.cjs +7 -4
  3. package/build/button/edit.cjs.map +3 -3
  4. package/build/columns/edit.cjs +4 -10
  5. package/build/columns/edit.cjs.map +2 -2
  6. package/build/comments/edit/placeholder.cjs +1 -1
  7. package/build/comments/edit/placeholder.cjs.map +2 -2
  8. package/build/cover/edit/inspector-controls.cjs +4 -2
  9. package/build/cover/edit/inspector-controls.cjs.map +2 -2
  10. package/build/freeform/migration-notice.cjs +1 -1
  11. package/build/freeform/migration-notice.cjs.map +1 -1
  12. package/build/home-link/block.json +7 -0
  13. package/build/home-link/edit.cjs +167 -24
  14. package/build/home-link/edit.cjs.map +3 -3
  15. package/build/html/edit.cjs +2 -4
  16. package/build/html/edit.cjs.map +2 -2
  17. package/build/html/modal.cjs +0 -4
  18. package/build/html/modal.cjs.map +2 -2
  19. package/build/image/block.json +4 -0
  20. package/build/image/deprecated.cjs +202 -4
  21. package/build/image/deprecated.cjs.map +3 -3
  22. package/build/image/image.cjs +80 -27
  23. package/build/image/image.cjs.map +2 -2
  24. package/build/image/index.cjs +23 -4
  25. package/build/image/index.cjs.map +2 -2
  26. package/build/image/save.cjs +25 -10
  27. package/build/image/save.cjs.map +2 -2
  28. package/build/image/transforms.cjs +15 -3
  29. package/build/image/transforms.cjs.map +2 -2
  30. package/build/image/use-open-image-media-editor-modal.cjs +29 -12
  31. package/build/image/use-open-image-media-editor-modal.cjs.map +2 -2
  32. package/build/list-item/hooks/use-enter.cjs +8 -4
  33. package/build/list-item/hooks/use-enter.cjs.map +3 -3
  34. package/build/list-item/hooks/use-space.cjs +8 -4
  35. package/build/list-item/hooks/use-space.cjs.map +3 -3
  36. package/build/navigation-link/edit.cjs +2 -1
  37. package/build/navigation-link/edit.cjs.map +2 -2
  38. package/build/navigation-link/shared/use-handle-link-change.cjs +19 -3
  39. package/build/navigation-link/shared/use-handle-link-change.cjs.map +3 -3
  40. package/build/navigation-submenu/edit.cjs +8 -22
  41. package/build/navigation-submenu/edit.cjs.map +2 -2
  42. package/build/paragraph/use-enter.cjs +8 -4
  43. package/build/paragraph/use-enter.cjs.map +3 -3
  44. package/build/post-date/edit.cjs +9 -1
  45. package/build/post-date/edit.cjs.map +2 -2
  46. package/build/post-featured-image/edit.cjs +0 -1
  47. package/build/post-featured-image/edit.cjs.map +2 -2
  48. package/build/site-logo/edit.cjs +1 -1
  49. package/build/site-logo/edit.cjs.map +2 -2
  50. package/build/social-link/edit.cjs.map +3 -3
  51. package/build/tab-list/edit.cjs +2 -0
  52. package/build/tab-list/edit.cjs.map +2 -2
  53. package/build/tab-panels/edit.cjs +5 -1
  54. package/build/tab-panels/edit.cjs.map +2 -2
  55. package/build/table/edit.cjs +1 -0
  56. package/build/table/edit.cjs.map +2 -2
  57. package/build/tabs/edit.cjs +1 -36
  58. package/build/tabs/edit.cjs.map +2 -2
  59. package/build-module/button/edit.mjs +12 -5
  60. package/build-module/button/edit.mjs.map +2 -2
  61. package/build-module/columns/edit.mjs +4 -10
  62. package/build-module/columns/edit.mjs.map +2 -2
  63. package/build-module/comments/edit/placeholder.mjs +1 -1
  64. package/build-module/comments/edit/placeholder.mjs.map +2 -2
  65. package/build-module/cover/edit/inspector-controls.mjs +4 -3
  66. package/build-module/cover/edit/inspector-controls.mjs.map +2 -2
  67. package/build-module/freeform/migration-notice.mjs +1 -1
  68. package/build-module/freeform/migration-notice.mjs.map +1 -1
  69. package/build-module/home-link/block.json +7 -0
  70. package/build-module/home-link/edit.mjs +181 -26
  71. package/build-module/home-link/edit.mjs.map +2 -2
  72. package/build-module/html/edit.mjs +2 -4
  73. package/build-module/html/edit.mjs.map +2 -2
  74. package/build-module/html/modal.mjs +0 -4
  75. package/build-module/html/modal.mjs.map +2 -2
  76. package/build-module/image/block.json +4 -0
  77. package/build-module/image/deprecated.mjs +204 -5
  78. package/build-module/image/deprecated.mjs.map +2 -2
  79. package/build-module/image/image.mjs +81 -27
  80. package/build-module/image/image.mjs.map +2 -2
  81. package/build-module/image/index.mjs +23 -4
  82. package/build-module/image/index.mjs.map +2 -2
  83. package/build-module/image/save.mjs +25 -10
  84. package/build-module/image/save.mjs.map +2 -2
  85. package/build-module/image/transforms.mjs +15 -3
  86. package/build-module/image/transforms.mjs.map +2 -2
  87. package/build-module/image/use-open-image-media-editor-modal.mjs +29 -12
  88. package/build-module/image/use-open-image-media-editor-modal.mjs.map +2 -2
  89. package/build-module/list-item/hooks/use-enter.mjs +12 -5
  90. package/build-module/list-item/hooks/use-enter.mjs.map +2 -2
  91. package/build-module/list-item/hooks/use-space.mjs +12 -5
  92. package/build-module/list-item/hooks/use-space.mjs.map +2 -2
  93. package/build-module/navigation-link/edit.mjs +2 -1
  94. package/build-module/navigation-link/edit.mjs.map +2 -2
  95. package/build-module/navigation-link/shared/use-handle-link-change.mjs +19 -3
  96. package/build-module/navigation-link/shared/use-handle-link-change.mjs.map +2 -2
  97. package/build-module/navigation-submenu/edit.mjs +9 -23
  98. package/build-module/navigation-submenu/edit.mjs.map +2 -2
  99. package/build-module/paragraph/use-enter.mjs +12 -5
  100. package/build-module/paragraph/use-enter.mjs.map +2 -2
  101. package/build-module/post-date/edit.mjs +9 -1
  102. package/build-module/post-date/edit.mjs.map +2 -2
  103. package/build-module/post-featured-image/edit.mjs +0 -1
  104. package/build-module/post-featured-image/edit.mjs.map +2 -2
  105. package/build-module/site-logo/edit.mjs +1 -1
  106. package/build-module/site-logo/edit.mjs.map +2 -2
  107. package/build-module/social-link/edit.mjs +2 -2
  108. package/build-module/social-link/edit.mjs.map +2 -2
  109. package/build-module/tab-list/edit.mjs +2 -0
  110. package/build-module/tab-list/edit.mjs.map +2 -2
  111. package/build-module/tab-panels/edit.mjs +5 -1
  112. package/build-module/tab-panels/edit.mjs.map +2 -2
  113. package/build-module/table/edit.mjs +1 -0
  114. package/build-module/table/edit.mjs.map +2 -2
  115. package/build-module/tabs/edit.mjs +2 -37
  116. package/build-module/tabs/edit.mjs.map +2 -2
  117. package/build-style/breadcrumbs/style-rtl.css +1 -1
  118. package/build-style/breadcrumbs/style.css +1 -1
  119. package/build-style/editor-rtl.css +0 -11
  120. package/build-style/editor.css +0 -11
  121. package/build-style/gallery/editor-rtl.css +0 -11
  122. package/build-style/gallery/editor.css +0 -11
  123. package/build-style/style-rtl.css +1 -1
  124. package/build-style/style.css +1 -1
  125. package/package.json +42 -42
  126. package/src/block/edit-title.native.js +3 -3
  127. package/src/block/edit.native.js +2 -2
  128. package/src/breadcrumbs/style.scss +1 -1
  129. package/src/button/edit.js +14 -5
  130. package/src/columns/edit.js +3 -9
  131. package/src/comments/edit/placeholder.js +1 -1
  132. package/src/cover/controls.native.js +2 -2
  133. package/src/cover/edit/inspector-controls.js +8 -7
  134. package/src/cover/edit.native.js +6 -4
  135. package/src/cover/focal-point-settings-button.native.js +2 -2
  136. package/src/cover/test/edit.js +32 -31
  137. package/src/embed/embed-no-preview.native.js +7 -3
  138. package/src/embed/embed-placeholder.native.js +2 -2
  139. package/src/file/edit.native.js +2 -2
  140. package/src/freeform/migration-notice.js +1 -1
  141. package/src/gallery/editor.scss +0 -14
  142. package/src/home-link/block.json +7 -0
  143. package/src/home-link/edit.js +185 -22
  144. package/src/home-link/index.php +14 -2
  145. package/src/html/edit.js +14 -12
  146. package/src/html/modal.js +0 -5
  147. package/src/image/block.json +4 -0
  148. package/src/image/deprecated.js +236 -4
  149. package/src/image/edit.native.js +2 -2
  150. package/src/image/image.js +116 -41
  151. package/src/image/index.js +20 -1
  152. package/src/image/index.php +1 -1
  153. package/src/image/save.js +39 -12
  154. package/src/image/test/use-open-image-media-editor-modal.js +60 -0
  155. package/src/image/transforms.js +21 -5
  156. package/src/image/use-open-image-media-editor-modal.js +34 -16
  157. package/src/latest-posts/edit.native.js +2 -2
  158. package/src/list-item/hooks/use-enter.js +15 -5
  159. package/src/list-item/hooks/use-space.js +15 -5
  160. package/src/list-item/list-style-type.native.js +2 -2
  161. package/src/media-text/media-container.native.js +7 -3
  162. package/src/missing/edit.native.js +4 -4
  163. package/src/missing/test/edit.native.js +3 -3
  164. package/src/navigation/test/use-navigation-menu.js +8 -2
  165. package/src/navigation-link/edit.js +1 -0
  166. package/src/navigation-link/shared/test/use-handle-link-change.test.js +212 -0
  167. package/src/navigation-link/shared/use-handle-link-change.js +36 -9
  168. package/src/navigation-link/test/__snapshots__/hooks.js.snap +134 -45
  169. package/src/navigation-submenu/edit.js +11 -28
  170. package/src/navigation-submenu/index.php +13 -0
  171. package/src/paragraph/use-enter.js +19 -5
  172. package/src/post-date/edit.js +7 -3
  173. package/src/post-featured-image/edit.js +0 -1
  174. package/src/search/edit.native.js +2 -2
  175. package/src/search/test/edit.native.js +2 -2
  176. package/src/site-logo/edit.js +2 -1
  177. package/src/social-link/edit.js +2 -2
  178. package/src/tab-list/edit.js +3 -0
  179. package/src/tab-panels/edit.js +10 -1
  180. package/src/table/edit.js +1 -0
  181. package/src/tabs/edit.js +14 -42
  182. package/src/video/edit.native.js +3 -3
@@ -41,7 +41,7 @@ function getFirstAnchorAttributeFormHTML( html, attributeName ) {
41
41
 
42
42
  const imageSchema = {
43
43
  img: {
44
- attributes: [ 'src', 'alt', 'title' ],
44
+ attributes: [ 'src', 'alt', 'title', 'width', 'height' ],
45
45
  classes: [
46
46
  'alignleft',
47
47
  'aligncenter',
@@ -52,6 +52,14 @@ const imageSchema = {
52
52
  },
53
53
  };
54
54
 
55
+ // Read a pixel dimension from an `<img>` HTML attribute and normalise it to
56
+ // the `<value>px` form the Image block stores in its `width` / `height`
57
+ // attributes. Non-integer values (e.g. `50%`) are dropped because the block
58
+ // attribute round-trips through inline styles that expect pixel units.
59
+ function parsePixelDimension( value ) {
60
+ return value && /^\d+$/.test( value ) ? `${ value }px` : undefined;
61
+ }
62
+
55
63
  const schema = ( { phrasingContentSchema } ) => ( {
56
64
  figure: {
57
65
  require: [ 'img' ],
@@ -77,12 +85,10 @@ const transforms = {
77
85
  node.nodeName === 'FIGURE' && !! node.querySelector( 'img' ),
78
86
  schema,
79
87
  transform: ( node ) => {
88
+ const img = node.querySelector( 'img' );
80
89
  // Search both figure and image classes. Alignment could be
81
90
  // set on either. ID is set on the image.
82
- const className =
83
- node.className +
84
- ' ' +
85
- node.querySelector( 'img' ).className;
91
+ const className = node.className + ' ' + img.className;
86
92
  const alignMatches =
87
93
  /(?:^|\s)align(left|center|right)(?:$|\s)/.exec(
88
94
  className
@@ -108,6 +114,14 @@ const transforms = {
108
114
  anchorElement && anchorElement.className
109
115
  ? anchorElement.className
110
116
  : undefined;
117
+ // Preserve explicit pixel dimensions set on the source `<img>`
118
+ // (e.g. legacy "Add Media" output: `<img width="77" height="77">`).
119
+ const width = parsePixelDimension(
120
+ img.getAttribute( 'width' )
121
+ );
122
+ const height = parsePixelDimension(
123
+ img.getAttribute( 'height' )
124
+ );
111
125
  const attributes = getBlockAttributes(
112
126
  'core/image',
113
127
  node.outerHTML,
@@ -119,6 +133,8 @@ const transforms = {
119
133
  rel,
120
134
  linkClass,
121
135
  anchor,
136
+ width,
137
+ height,
122
138
  }
123
139
  );
124
140
 
@@ -147,11 +147,16 @@ export function useOpenImageMediaEditorModal( { attributes, setAttributes } ) {
147
147
  select( blockEditorStore ).getSettings()[ openMediaEditorModalKey ],
148
148
  []
149
149
  );
150
- // Track the block's current alt and caption in a ref so handleMediaUpdate
151
- // can read the latest values without being listed as a dependency (which
152
- // would recreate the callback and re-register the onUpdate handler on every
153
- // keystroke while the modal is open).
154
- const blockMetadataRef = useRef( { alt, caption: caption?.toString() } );
150
+ // Track the block's current attachment and metadata in a ref so
151
+ // handleMediaUpdate can read the latest values without being listed as
152
+ // dependencies (which would recreate the callback and re-register the
153
+ // onUpdate handler on every block change while the modal is open).
154
+ const blockAttributesRef = useRef( {
155
+ id,
156
+ url,
157
+ alt,
158
+ caption: caption?.toString(),
159
+ } );
155
160
  // Snapshot of the attachment's metadata taken just before the modal opens,
156
161
  // used as the baseline for detecting what changed during the editing session.
157
162
  const mediaEditorMetadataBaselineRef = useRef();
@@ -160,8 +165,13 @@ export function useOpenImageMediaEditorModal( { attributes, setAttributes } ) {
160
165
  const mediaEditorMetadataSyncRequestRef = useRef( 0 );
161
166
 
162
167
  useEffect( () => {
163
- blockMetadataRef.current = { alt, caption: caption?.toString() };
164
- }, [ alt, caption ] );
168
+ blockAttributesRef.current = {
169
+ id,
170
+ url,
171
+ alt,
172
+ caption: caption?.toString(),
173
+ };
174
+ }, [ alt, caption, id, url ] );
165
175
 
166
176
  const getCachedAttachmentRecord = useCallback(
167
177
  ( attachmentId ) => {
@@ -245,9 +255,16 @@ export function useOpenImageMediaEditorModal( { attributes, setAttributes } ) {
245
255
  const syncRequest = ++mediaEditorMetadataSyncRequestRef.current;
246
256
  const nextAttributes = {};
247
257
 
248
- if ( newId !== id ) {
258
+ const currentBlockAttributes = blockAttributesRef.current;
259
+
260
+ if ( newId !== currentBlockAttributes.id ) {
249
261
  nextAttributes.id = newId;
250
- nextAttributes.url = newUrl ?? url;
262
+ nextAttributes.url = newUrl ?? currentBlockAttributes.url;
263
+ blockAttributesRef.current = {
264
+ ...blockAttributesRef.current,
265
+ id: nextAttributes.id,
266
+ url: nextAttributes.url,
267
+ };
251
268
  }
252
269
 
253
270
  if ( originalAttachment ) {
@@ -270,27 +287,28 @@ export function useOpenImageMediaEditorModal( { attributes, setAttributes } ) {
270
287
  // has independently customised on the block (i.e. values
271
288
  // that don't match the pre-session attachment metadata)
272
289
  // are left untouched.
290
+ const latestBlockAttributes = blockAttributesRef.current;
273
291
  const resolvedMetadataAttributes =
274
292
  getSyncedImageBlockAttributes(
275
- blockMetadataRef.current,
293
+ latestBlockAttributes,
276
294
  originalAttachment,
277
295
  resolvedAttachment
278
296
  );
279
297
 
280
298
  if ( Object.keys( resolvedMetadataAttributes ).length ) {
281
299
  Object.assign( nextAttributes, resolvedMetadataAttributes );
282
- blockMetadataRef.current = {
283
- ...blockMetadataRef.current,
284
- ...resolvedMetadataAttributes,
285
- };
286
300
  }
287
301
  }
288
302
 
289
303
  if ( Object.keys( nextAttributes ).length ) {
304
+ blockAttributesRef.current = {
305
+ ...blockAttributesRef.current,
306
+ ...nextAttributes,
307
+ };
290
308
  setAttributes( nextAttributes );
291
309
  }
292
310
  },
293
- [ id, resolveFreshAttachmentRecord, setAttributes, url ]
311
+ [ resolveFreshAttachmentRecord, setAttributes ]
294
312
  );
295
313
 
296
314
  const openImageMediaEditorModal = useCallback( async () => {
@@ -306,7 +324,7 @@ export function useOpenImageMediaEditorModal( { attributes, setAttributes } ) {
306
324
  const cachedAttachmentRecord = getCachedAttachmentRecord( id );
307
325
  const fallbackAttachmentRecord =
308
326
  getAttachmentFallbackForEmptyBlockMetadata(
309
- blockMetadataRef.current
327
+ blockAttributesRef.current
310
328
  );
311
329
  const resolvedAttachmentRecord = hasKnownAttachmentMetadata(
312
330
  cachedAttachmentRecord
@@ -17,7 +17,7 @@ import {
17
17
  } from '@wordpress/block-editor';
18
18
  import apiFetch from '@wordpress/api-fetch';
19
19
  import {
20
- Icon,
20
+ Icon as WCIcon,
21
21
  PanelBody,
22
22
  ToggleControl,
23
23
  RangeControl,
@@ -265,7 +265,7 @@ class LatestPostsEdit extends Component {
265
265
  >
266
266
  <View style={ blockStyle }>
267
267
  { isSelected && this.getInspectorControls() }
268
- <Icon icon={ icon } { ...iconStyle } />
268
+ <WCIcon icon={ icon } { ...iconStyle } />
269
269
  <Text style={ titleStyle }>{ blockTitle }</Text>
270
270
  <Text style={ styles.latestPostBlockSubtitle }>
271
271
  { __( 'CUSTOMIZE' ) }
@@ -7,7 +7,10 @@ import {
7
7
  cloneBlock,
8
8
  } from '@wordpress/blocks';
9
9
  import { useRef } from '@wordpress/element';
10
- import { useRefEffect } from '@wordpress/compose';
10
+ import {
11
+ useRefEffect,
12
+ privateApis as composePrivateApis,
13
+ } from '@wordpress/compose';
11
14
  import { ENTER } from '@wordpress/keycodes';
12
15
  import { useSelect, useDispatch } from '@wordpress/data';
13
16
  import { store as blockEditorStore } from '@wordpress/block-editor';
@@ -16,6 +19,9 @@ import { store as blockEditorStore } from '@wordpress/block-editor';
16
19
  * Internal dependencies
17
20
  */
18
21
  import useOutdentListItem from './use-outdent-list-item';
22
+ import { unlock } from '../../lock-unlock';
23
+
24
+ const { subscribeDelegatedListener } = unlock( composePrivateApis );
19
25
 
20
26
  export default function useEnter( props ) {
21
27
  const { replaceBlocks, selectionChange } = useDispatch( blockEditorStore );
@@ -82,9 +88,13 @@ export default function useEnter( props ) {
82
88
  selectionChange( middle.clientId );
83
89
  }
84
90
 
85
- element.addEventListener( 'keydown', onKeyDown );
86
- return () => {
87
- element.removeEventListener( 'keydown', onKeyDown );
88
- };
91
+ // Capture phase so we run before writing-flow's ancestor-bubble
92
+ // keydown handlers that gate on `event.defaultPrevented`.
93
+ return subscribeDelegatedListener(
94
+ element,
95
+ 'keydown',
96
+ onKeyDown,
97
+ true
98
+ );
89
99
  }, [] );
90
100
  }
@@ -1,7 +1,10 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { useRefEffect } from '@wordpress/compose';
4
+ import {
5
+ useRefEffect,
6
+ privateApis as composePrivateApis,
7
+ } from '@wordpress/compose';
5
8
  import { SPACE, TAB } from '@wordpress/keycodes';
6
9
  import { store as blockEditorStore } from '@wordpress/block-editor';
7
10
  import { useSelect } from '@wordpress/data';
@@ -11,6 +14,9 @@ import { useSelect } from '@wordpress/data';
11
14
  */
12
15
  import useIndentListItem from './use-indent-list-item';
13
16
  import useOutdentListItem from './use-outdent-list-item';
17
+ import { unlock } from '../../lock-unlock';
18
+
19
+ const { subscribeDelegatedListener } = unlock( composePrivateApis );
14
20
 
15
21
  export default function useSpace( clientId ) {
16
22
  const { getSelectionStart, getSelectionEnd, getBlockIndex } =
@@ -55,10 +61,14 @@ export default function useSpace( clientId ) {
55
61
  }
56
62
  }
57
63
 
58
- element.addEventListener( 'keydown', onKeyDown );
59
- return () => {
60
- element.removeEventListener( 'keydown', onKeyDown );
61
- };
64
+ // Capture phase so we run before writing-flow's ancestor-bubble
65
+ // keydown handlers that gate on `event.defaultPrevented`.
66
+ return subscribeDelegatedListener(
67
+ element,
68
+ 'keydown',
69
+ onKeyDown,
70
+ true
71
+ );
62
72
  },
63
73
  [ clientId, indentListItem ]
64
74
  );
@@ -6,7 +6,7 @@ import { View, Text } from 'react-native';
6
6
  /**
7
7
  * WordPress dependencies
8
8
  */
9
- import { Icon } from '@wordpress/components';
9
+ import { Icon as WCIcon } from '@wordpress/components';
10
10
  import { Platform } from '@wordpress/element';
11
11
  import { usePreferredColorSchemeStyle } from '@wordpress/compose';
12
12
 
@@ -85,7 +85,7 @@ function IconList( { fontSize, color, defaultFontSize, indentationLevel } ) {
85
85
 
86
86
  return (
87
87
  <View style={ listStyles }>
88
- <Icon icon={ listIcon } size={ iconSize } />
88
+ <WCIcon icon={ listIcon } size={ iconSize } />
89
89
  </View>
90
90
  );
91
91
  }
@@ -12,7 +12,11 @@ import {
12
12
  requestImageUploadCancelDialog,
13
13
  requestImageFullscreenPreview,
14
14
  } from '@wordpress/react-native-bridge';
15
- import { Icon, Image, IMAGE_DEFAULT_FOCAL_POINT } from '@wordpress/components';
15
+ import {
16
+ Icon as WCIcon,
17
+ Image,
18
+ IMAGE_DEFAULT_FOCAL_POINT,
19
+ } from '@wordpress/components';
16
20
  import {
17
21
  MEDIA_TYPE_IMAGE,
18
22
  MEDIA_TYPE_VIDEO,
@@ -117,7 +121,7 @@ class MediaContainer extends Component {
117
121
  styles.iconRetryVideoDark
118
122
  );
119
123
 
120
- return <Icon icon={ SvgIconRetry } { ...iconStyle } />;
124
+ return <WCIcon icon={ SvgIconRetry } { ...iconStyle } />;
121
125
  case ICON_TYPE.PLACEHOLDER:
122
126
  iconStyle = getStylesFromColorScheme(
123
127
  styles.iconPlaceholder,
@@ -125,7 +129,7 @@ class MediaContainer extends Component {
125
129
  );
126
130
  break;
127
131
  }
128
- return <Icon icon={ icon } { ...iconStyle } />;
132
+ return <WCIcon icon={ icon } { ...iconStyle } />;
129
133
  }
130
134
 
131
135
  updateMediaProgress( payload ) {
@@ -6,9 +6,8 @@ import { View, Text, TouchableOpacity } from 'react-native';
6
6
  /**
7
7
  * WordPress dependencies
8
8
  */
9
- import { Icon } from '@wordpress/components';
9
+ import { Icon as WCIcon } from '@wordpress/components';
10
10
  import { compose, withPreferredColorScheme } from '@wordpress/compose';
11
- import { coreBlocks } from '@wordpress/block-library';
12
11
  import { normalizeIconObject, rawHandler, serialize } from '@wordpress/blocks';
13
12
  import { Component } from '@wordpress/element';
14
13
  import { __, _x, sprintf } from '@wordpress/i18n';
@@ -21,6 +20,7 @@ import {
21
20
  } from '@wordpress/block-editor';
22
21
  import { store as noticesStore } from '@wordpress/notices';
23
22
  import { requestUnsupportedBlockFallback } from '@wordpress/react-native-bridge';
23
+ import { coreBlocks } from '@wordpress/block-library';
24
24
 
25
25
  /**
26
26
  * Internal dependencies
@@ -116,7 +116,7 @@ export class UnsupportedBlockEdit extends Component {
116
116
  accessibilityRole="button"
117
117
  accessibilityHint={ __( 'Tap here to show help' ) }
118
118
  >
119
- <Icon
119
+ <WCIcon
120
120
  className="unsupported-icon-help"
121
121
  label={ __( 'Help icon' ) }
122
122
  icon={ help }
@@ -245,7 +245,7 @@ export class UnsupportedBlockEdit extends Component {
245
245
  { ! this.canEditUnsupportedBlock() &&
246
246
  this.renderHelpIcon() }
247
247
  <View style={ styles.unsupportedBlockHeader }>
248
- <Icon
248
+ <WCIcon
249
249
  className={ iconClassName }
250
250
  icon={ icon && icon.src ? icon.src : icon }
251
251
  fill={ iconStyle.color }
@@ -7,7 +7,7 @@ import { Text } from 'react-native';
7
7
  /**
8
8
  * WordPress dependencies
9
9
  */
10
- import { BottomSheet, Icon } from '@wordpress/components';
10
+ import { BottomSheet, Icon as WCIcon } from '@wordpress/components';
11
11
  import { help, plugins } from '@wordpress/icons';
12
12
  import { storeConfig } from '@wordpress/block-editor';
13
13
  jest.mock( '@wordpress/block-editor/src/store/selectors' );
@@ -39,7 +39,7 @@ describe( 'Missing block', () => {
39
39
  describe( 'help modal', () => {
40
40
  it( 'renders help icon', () => {
41
41
  const testInstance = getTestComponentWithContent();
42
- const icons = testInstance.UNSAFE_getAllByType( Icon );
42
+ const icons = testInstance.UNSAFE_getAllByType( WCIcon );
43
43
  expect( icons.length ).toBe( 2 );
44
44
  expect( icons[ 0 ].props.icon ).toBe( help );
45
45
  } );
@@ -66,7 +66,7 @@ describe( 'Missing block', () => {
66
66
 
67
67
  it( 'renders admin plugins icon', () => {
68
68
  const testInstance = getTestComponentWithContent();
69
- const icons = testInstance.UNSAFE_getAllByType( Icon );
69
+ const icons = testInstance.UNSAFE_getAllByType( WCIcon );
70
70
  expect( icons.length ).toBe( 2 );
71
71
  expect( icons[ 1 ].props.icon ).toBe( plugins );
72
72
  } );
@@ -70,8 +70,14 @@ function resolveRecords( registry, menus ) {
70
70
  function resolveReadPermission( registry, allowed ) {
71
71
  const dispatch = registry.dispatch( coreStore );
72
72
  dispatch.receiveUserPermission( 'read/postType/wp_navigation', allowed );
73
- dispatch.startResolution( 'canUser', [ 'read', BASE_ENTITY ] );
74
- dispatch.finishResolution( 'canUser', [ 'read', BASE_ENTITY ] );
73
+ dispatch.startResolution( 'canUser', [
74
+ 'read',
75
+ { kind: 'postType', name: 'wp_navigation' },
76
+ ] );
77
+ dispatch.finishResolution( 'canUser', [
78
+ 'read',
79
+ { kind: 'postType', name: 'wp_navigation' },
80
+ ] );
75
81
  }
76
82
 
77
83
  function resolveReadRecordPermission( registry, ref, allowed ) {
@@ -174,6 +174,7 @@ export default function NavigationLinkEdit( {
174
174
  clientId,
175
175
  attributes,
176
176
  setAttributes,
177
+ allowTextUpdate: true,
177
178
  } );
178
179
 
179
180
  const [ isInvalid, isDraft ] = useIsInvalidLink(
@@ -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,