@wordpress/block-library 9.46.0 → 9.48.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 (176) 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/cover/edit/inspector-controls.cjs +20 -6
  7. package/build/cover/edit/inspector-controls.cjs.map +2 -2
  8. package/build/freeform/migration-notice.cjs +1 -1
  9. package/build/freeform/migration-notice.cjs.map +1 -1
  10. package/build/home-link/block.json +7 -0
  11. package/build/home-link/edit.cjs +167 -24
  12. package/build/home-link/edit.cjs.map +3 -3
  13. package/build/html/edit.cjs +2 -4
  14. package/build/html/edit.cjs.map +2 -2
  15. package/build/html/modal.cjs +0 -4
  16. package/build/html/modal.cjs.map +2 -2
  17. package/build/image/block.json +4 -0
  18. package/build/image/deprecated.cjs +202 -4
  19. package/build/image/deprecated.cjs.map +3 -3
  20. package/build/image/image.cjs +94 -30
  21. package/build/image/image.cjs.map +2 -2
  22. package/build/image/index.cjs +23 -4
  23. package/build/image/index.cjs.map +2 -2
  24. package/build/image/save.cjs +25 -10
  25. package/build/image/save.cjs.map +2 -2
  26. package/build/image/transforms.cjs +15 -3
  27. package/build/image/transforms.cjs.map +2 -2
  28. package/build/image/use-open-image-media-editor-modal.cjs +37 -14
  29. package/build/image/use-open-image-media-editor-modal.cjs.map +2 -2
  30. package/build/list-item/hooks/use-enter.cjs +8 -4
  31. package/build/list-item/hooks/use-enter.cjs.map +3 -3
  32. package/build/list-item/hooks/use-space.cjs +8 -4
  33. package/build/list-item/hooks/use-space.cjs.map +3 -3
  34. package/build/navigation-link/edit.cjs +2 -1
  35. package/build/navigation-link/edit.cjs.map +2 -2
  36. package/build/navigation-link/shared/use-handle-link-change.cjs +19 -3
  37. package/build/navigation-link/shared/use-handle-link-change.cjs.map +3 -3
  38. package/build/navigation-submenu/edit.cjs +8 -22
  39. package/build/navigation-submenu/edit.cjs.map +2 -2
  40. package/build/paragraph/use-enter.cjs +8 -4
  41. package/build/paragraph/use-enter.cjs.map +3 -3
  42. package/build/post-date/edit.cjs +9 -1
  43. package/build/post-date/edit.cjs.map +2 -2
  44. package/build/post-featured-image/edit.cjs +6 -5
  45. package/build/post-featured-image/edit.cjs.map +2 -2
  46. package/build/site-logo/edit.cjs +5 -2
  47. package/build/site-logo/edit.cjs.map +2 -2
  48. package/build/social-link/edit.cjs.map +3 -3
  49. package/build/tab-list/edit.cjs +2 -0
  50. package/build/tab-list/edit.cjs.map +2 -2
  51. package/build/tab-panels/edit.cjs +5 -1
  52. package/build/tab-panels/edit.cjs.map +2 -2
  53. package/build/table/edit.cjs +1 -0
  54. package/build/table/edit.cjs.map +2 -2
  55. package/build/tabs/edit.cjs +1 -36
  56. package/build/tabs/edit.cjs.map +2 -2
  57. package/build-module/button/edit.mjs +12 -5
  58. package/build-module/button/edit.mjs.map +2 -2
  59. package/build-module/columns/edit.mjs +4 -10
  60. package/build-module/columns/edit.mjs.map +2 -2
  61. package/build-module/cover/edit/inspector-controls.mjs +20 -7
  62. package/build-module/cover/edit/inspector-controls.mjs.map +2 -2
  63. package/build-module/freeform/migration-notice.mjs +1 -1
  64. package/build-module/freeform/migration-notice.mjs.map +1 -1
  65. package/build-module/home-link/block.json +7 -0
  66. package/build-module/home-link/edit.mjs +181 -26
  67. package/build-module/home-link/edit.mjs.map +2 -2
  68. package/build-module/html/edit.mjs +2 -4
  69. package/build-module/html/edit.mjs.map +2 -2
  70. package/build-module/html/modal.mjs +0 -4
  71. package/build-module/html/modal.mjs.map +2 -2
  72. package/build-module/image/block.json +4 -0
  73. package/build-module/image/deprecated.mjs +204 -5
  74. package/build-module/image/deprecated.mjs.map +2 -2
  75. package/build-module/image/image.mjs +96 -30
  76. package/build-module/image/image.mjs.map +2 -2
  77. package/build-module/image/index.mjs +23 -4
  78. package/build-module/image/index.mjs.map +2 -2
  79. package/build-module/image/save.mjs +25 -10
  80. package/build-module/image/save.mjs.map +2 -2
  81. package/build-module/image/transforms.mjs +15 -3
  82. package/build-module/image/transforms.mjs.map +2 -2
  83. package/build-module/image/use-open-image-media-editor-modal.mjs +37 -14
  84. package/build-module/image/use-open-image-media-editor-modal.mjs.map +2 -2
  85. package/build-module/list-item/hooks/use-enter.mjs +12 -5
  86. package/build-module/list-item/hooks/use-enter.mjs.map +2 -2
  87. package/build-module/list-item/hooks/use-space.mjs +12 -5
  88. package/build-module/list-item/hooks/use-space.mjs.map +2 -2
  89. package/build-module/navigation-link/edit.mjs +2 -1
  90. package/build-module/navigation-link/edit.mjs.map +2 -2
  91. package/build-module/navigation-link/shared/use-handle-link-change.mjs +19 -3
  92. package/build-module/navigation-link/shared/use-handle-link-change.mjs.map +2 -2
  93. package/build-module/navigation-submenu/edit.mjs +9 -23
  94. package/build-module/navigation-submenu/edit.mjs.map +2 -2
  95. package/build-module/paragraph/use-enter.mjs +12 -5
  96. package/build-module/paragraph/use-enter.mjs.map +2 -2
  97. package/build-module/post-date/edit.mjs +9 -1
  98. package/build-module/post-date/edit.mjs.map +2 -2
  99. package/build-module/post-featured-image/edit.mjs +6 -5
  100. package/build-module/post-featured-image/edit.mjs.map +2 -2
  101. package/build-module/site-logo/edit.mjs +6 -2
  102. package/build-module/site-logo/edit.mjs.map +2 -2
  103. package/build-module/social-link/edit.mjs +2 -2
  104. package/build-module/social-link/edit.mjs.map +2 -2
  105. package/build-module/tab-list/edit.mjs +2 -0
  106. package/build-module/tab-list/edit.mjs.map +2 -2
  107. package/build-module/tab-panels/edit.mjs +5 -1
  108. package/build-module/tab-panels/edit.mjs.map +2 -2
  109. package/build-module/table/edit.mjs +1 -0
  110. package/build-module/table/edit.mjs.map +2 -2
  111. package/build-module/tabs/edit.mjs +2 -37
  112. package/build-module/tabs/edit.mjs.map +2 -2
  113. package/build-style/breadcrumbs/style-rtl.css +1 -1
  114. package/build-style/breadcrumbs/style.css +1 -1
  115. package/build-style/editor-rtl.css +0 -11
  116. package/build-style/editor.css +0 -11
  117. package/build-style/gallery/editor-rtl.css +0 -11
  118. package/build-style/gallery/editor.css +0 -11
  119. package/build-style/style-rtl.css +1 -1
  120. package/build-style/style.css +1 -1
  121. package/package.json +40 -40
  122. package/src/block/edit-title.native.js +3 -3
  123. package/src/block/edit.native.js +2 -2
  124. package/src/breadcrumbs/style.scss +1 -1
  125. package/src/button/edit.js +14 -5
  126. package/src/columns/edit.js +3 -9
  127. package/src/cover/controls.native.js +2 -2
  128. package/src/cover/edit/inspector-controls.js +69 -52
  129. package/src/cover/edit.native.js +6 -4
  130. package/src/cover/focal-point-settings-button.native.js +2 -2
  131. package/src/cover/test/edit.js +70 -31
  132. package/src/embed/embed-no-preview.native.js +7 -3
  133. package/src/embed/embed-placeholder.native.js +2 -2
  134. package/src/file/edit.native.js +2 -2
  135. package/src/freeform/migration-notice.js +1 -1
  136. package/src/gallery/editor.scss +0 -14
  137. package/src/home-link/block.json +7 -0
  138. package/src/home-link/edit.js +185 -22
  139. package/src/home-link/index.php +14 -2
  140. package/src/html/edit.js +14 -12
  141. package/src/html/modal.js +0 -5
  142. package/src/image/block.json +4 -0
  143. package/src/image/deprecated.js +236 -4
  144. package/src/image/edit.native.js +2 -2
  145. package/src/image/image.js +166 -76
  146. package/src/image/index.js +20 -1
  147. package/src/image/index.php +1 -1
  148. package/src/image/save.js +39 -12
  149. package/src/image/test/use-open-image-media-editor-modal.js +101 -0
  150. package/src/image/transforms.js +21 -5
  151. package/src/image/use-open-image-media-editor-modal.js +41 -17
  152. package/src/latest-posts/edit.native.js +2 -2
  153. package/src/list-item/hooks/use-enter.js +15 -5
  154. package/src/list-item/hooks/use-space.js +15 -5
  155. package/src/list-item/list-style-type.native.js +2 -2
  156. package/src/media-text/media-container.native.js +7 -3
  157. package/src/missing/edit.native.js +4 -4
  158. package/src/missing/test/edit.native.js +3 -3
  159. package/src/navigation/test/use-navigation-menu.js +8 -2
  160. package/src/navigation-link/edit.js +1 -0
  161. package/src/navigation-link/shared/test/use-handle-link-change.test.js +212 -0
  162. package/src/navigation-link/shared/use-handle-link-change.js +36 -9
  163. package/src/navigation-submenu/edit.js +11 -28
  164. package/src/navigation-submenu/index.php +13 -0
  165. package/src/paragraph/use-enter.js +19 -5
  166. package/src/post-date/edit.js +7 -3
  167. package/src/post-featured-image/edit.js +15 -11
  168. package/src/search/edit.native.js +2 -2
  169. package/src/search/test/edit.native.js +2 -2
  170. package/src/site-logo/edit.js +7 -1
  171. package/src/social-link/edit.js +2 -2
  172. package/src/tab-list/edit.js +3 -0
  173. package/src/tab-panels/edit.js +10 -1
  174. package/src/table/edit.js +1 -0
  175. package/src/tabs/edit.js +14 -42
  176. package/src/video/edit.native.js +3 -3
package/src/image/save.js CHANGED
@@ -37,6 +37,7 @@ export default function save( { attributes } ) {
37
37
  linkTarget,
38
38
  sizeSlug,
39
39
  title,
40
+ isDecorative,
40
41
  metadata: { bindings = {} } = {},
41
42
  } = attributes;
42
43
 
@@ -65,19 +66,45 @@ export default function save( { attributes } ) {
65
66
  src={ url }
66
67
  alt={ alt }
67
68
  className={ imageClasses || undefined }
68
- style={ {
69
- ...borderProps.style,
70
- ...shadowProps.style,
71
- aspectRatio,
72
- objectFit: scale,
73
- objectPosition:
74
- focalPoint && scale
75
- ? mediaPosition( focalPoint )
76
- : undefined,
77
- width,
78
- height,
79
- } }
69
+ style={ ( () => {
70
+ const style = {
71
+ ...borderProps.style,
72
+ ...shadowProps.style,
73
+ aspectRatio,
74
+ objectFit: scale,
75
+ objectPosition:
76
+ focalPoint && scale
77
+ ? mediaPosition( focalPoint )
78
+ : undefined,
79
+ };
80
+ // Only apply dimension styles when a width or height is provided.
81
+ if ( width !== undefined || height !== undefined ) {
82
+ // Only apply width when explicitly provided.
83
+ if ( width === 'auto' ) {
84
+ style.width = 'auto';
85
+ } else if ( width !== undefined && width !== null ) {
86
+ style.width =
87
+ typeof width === 'number' ? `${ width }px` : width;
88
+ }
89
+ // Force height to auto when unspecified to prevent
90
+ // theme CSS from squishing the image.
91
+ if (
92
+ height === 'auto' ||
93
+ height === undefined ||
94
+ height === null
95
+ ) {
96
+ style.height = 'auto';
97
+ } else {
98
+ style.height =
99
+ typeof height === 'number'
100
+ ? `${ height }px`
101
+ : height;
102
+ }
103
+ }
104
+ return style;
105
+ } )() }
80
106
  title={ title }
107
+ role={ isDecorative ? 'none' : undefined }
81
108
  />
82
109
  );
83
110
 
@@ -111,6 +111,45 @@ describe( 'useOpenImageMediaEditorModal', () => {
111
111
  jest.clearAllMocks();
112
112
  } );
113
113
 
114
+ it( 'passes an onClose handler for returning focus when the media editor closes', async () => {
115
+ const cropButton = document.createElement( 'button' );
116
+ const otherButton = document.createElement( 'button' );
117
+ document.body.append( cropButton, otherButton );
118
+ const registry = createRegistry();
119
+ useRegistry.mockReturnValue( registry );
120
+ const setAttributes = jest.fn();
121
+ const openMediaEditorModal = jest.fn();
122
+ mockMediaEditorModalSetting( openMediaEditorModal );
123
+ const onClose = () => cropButton.focus();
124
+ const { result } = renderHook( () =>
125
+ useOpenImageMediaEditorModal( {
126
+ attributes: {
127
+ id: 1,
128
+ url: 'original.jpg',
129
+ alt: '',
130
+ caption: '',
131
+ },
132
+ setAttributes,
133
+ onClose,
134
+ } )
135
+ );
136
+
137
+ try {
138
+ await act( async () => {
139
+ await result.current();
140
+ } );
141
+ otherButton.focus();
142
+ expect( otherButton ).toHaveFocus();
143
+
144
+ openMediaEditorModal.mock.calls[ 0 ][ 0 ].onClose();
145
+
146
+ expect( cropButton ).toHaveFocus();
147
+ } finally {
148
+ cropButton.remove();
149
+ otherButton.remove();
150
+ }
151
+ } );
152
+
114
153
  it( 'resolves fresh attachment metadata when the same attachment id has a stale cache', async () => {
115
154
  const originalAttachment = {
116
155
  id: 1,
@@ -187,6 +226,7 @@ describe( 'useOpenImageMediaEditorModal', () => {
187
226
  expect( openMediaEditorModal ).toHaveBeenCalledWith( {
188
227
  id: 1,
189
228
  onUpdate: expect.any( Function ),
229
+ onClose: undefined,
190
230
  } );
191
231
  expect( setAttributes ).toHaveBeenCalledWith( {
192
232
  alt: 'Updated alt',
@@ -229,6 +269,7 @@ describe( 'useOpenImageMediaEditorModal', () => {
229
269
  expect( openMediaEditorModal ).toHaveBeenCalledWith( {
230
270
  id: 1,
231
271
  onUpdate: expect.any( Function ),
272
+ onClose: undefined,
232
273
  } );
233
274
  expect( setAttributes ).toHaveBeenCalledWith( {
234
275
  caption: 'Updated attachment caption',
@@ -317,6 +358,66 @@ describe( 'useOpenImageMediaEditorModal', () => {
317
358
  } );
318
359
  } );
319
360
 
361
+ it( 'updates back to the previous attachment from the original modal callback', async () => {
362
+ const originalAttachment = {
363
+ id: 1,
364
+ alt_text: '',
365
+ caption: { raw: '' },
366
+ };
367
+ const croppedAttachment = {
368
+ id: 2,
369
+ alt_text: '',
370
+ caption: { raw: '' },
371
+ };
372
+ const deferredAttachment = createDeferred();
373
+ const registry = createRegistry( {
374
+ getEntityRecord: ( kind, name, attachmentId ) =>
375
+ attachmentId === 1 ? originalAttachment : undefined,
376
+ resolveGetEntityRecord: ( kind, name, attachmentId ) =>
377
+ attachmentId === 2 ? deferredAttachment.promise : undefined,
378
+ } );
379
+ useRegistry.mockReturnValue( registry );
380
+ const setAttributes = jest.fn();
381
+ const openMediaEditorModal = jest.fn();
382
+ mockMediaEditorModalSetting( openMediaEditorModal );
383
+ const { result } = renderHook(
384
+ ( { attributes } ) =>
385
+ useOpenImageMediaEditorModal( { attributes, setAttributes } ),
386
+ {
387
+ initialProps: {
388
+ attributes: {
389
+ id: 1,
390
+ url: 'original.jpg',
391
+ alt: '',
392
+ caption: '',
393
+ },
394
+ },
395
+ }
396
+ );
397
+
398
+ await act( async () => {
399
+ await result.current();
400
+ } );
401
+ const onUpdate = openMediaEditorModal.mock.calls[ 0 ][ 0 ].onUpdate;
402
+ let updatePromise;
403
+ await act( async () => {
404
+ updatePromise = onUpdate( { id: 2, url: 'cropped.jpg' } );
405
+ } );
406
+ await act( async () => {
407
+ await onUpdate( { id: 1, url: 'original.jpg' } );
408
+ } );
409
+ await act( async () => {
410
+ deferredAttachment.resolve( croppedAttachment );
411
+ await updatePromise;
412
+ } );
413
+
414
+ expect( setAttributes ).toHaveBeenCalledTimes( 1 );
415
+ expect( setAttributes ).toHaveBeenCalledWith( {
416
+ id: 1,
417
+ url: 'original.jpg',
418
+ } );
419
+ } );
420
+
320
421
  it( 'resolves fresh metadata when the new attachment id has an incomplete cached record', async () => {
321
422
  const originalAttachment = {
322
423
  id: 1,
@@ -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
 
@@ -136,7 +136,11 @@ function hasKnownAttachmentMetadata( attachment ) {
136
136
  return hasKnownAlt && hasKnownCaption;
137
137
  }
138
138
 
139
- export function useOpenImageMediaEditorModal( { attributes, setAttributes } ) {
139
+ export function useOpenImageMediaEditorModal( {
140
+ attributes,
141
+ setAttributes,
142
+ onClose,
143
+ } ) {
140
144
  // Keep this hook private to the Image block and pass the block attributes
141
145
  // object so the callsite stays compact. Destructure only the attributes
142
146
  // currently used for metadata sync; add more here if the sync policy grows.
@@ -147,11 +151,16 @@ export function useOpenImageMediaEditorModal( { attributes, setAttributes } ) {
147
151
  select( blockEditorStore ).getSettings()[ openMediaEditorModalKey ],
148
152
  []
149
153
  );
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() } );
154
+ // Track the block's current attachment and metadata in a ref so
155
+ // handleMediaUpdate can read the latest values without being listed as
156
+ // dependencies (which would recreate the callback and re-register the
157
+ // onUpdate handler on every block change while the modal is open).
158
+ const blockAttributesRef = useRef( {
159
+ id,
160
+ url,
161
+ alt,
162
+ caption: caption?.toString(),
163
+ } );
155
164
  // Snapshot of the attachment's metadata taken just before the modal opens,
156
165
  // used as the baseline for detecting what changed during the editing session.
157
166
  const mediaEditorMetadataBaselineRef = useRef();
@@ -160,8 +169,13 @@ export function useOpenImageMediaEditorModal( { attributes, setAttributes } ) {
160
169
  const mediaEditorMetadataSyncRequestRef = useRef( 0 );
161
170
 
162
171
  useEffect( () => {
163
- blockMetadataRef.current = { alt, caption: caption?.toString() };
164
- }, [ alt, caption ] );
172
+ blockAttributesRef.current = {
173
+ id,
174
+ url,
175
+ alt,
176
+ caption: caption?.toString(),
177
+ };
178
+ }, [ alt, caption, id, url ] );
165
179
 
166
180
  const getCachedAttachmentRecord = useCallback(
167
181
  ( attachmentId ) => {
@@ -245,9 +259,16 @@ export function useOpenImageMediaEditorModal( { attributes, setAttributes } ) {
245
259
  const syncRequest = ++mediaEditorMetadataSyncRequestRef.current;
246
260
  const nextAttributes = {};
247
261
 
248
- if ( newId !== id ) {
262
+ const currentBlockAttributes = blockAttributesRef.current;
263
+
264
+ if ( newId !== currentBlockAttributes.id ) {
249
265
  nextAttributes.id = newId;
250
- nextAttributes.url = newUrl ?? url;
266
+ nextAttributes.url = newUrl ?? currentBlockAttributes.url;
267
+ blockAttributesRef.current = {
268
+ ...blockAttributesRef.current,
269
+ id: nextAttributes.id,
270
+ url: nextAttributes.url,
271
+ };
251
272
  }
252
273
 
253
274
  if ( originalAttachment ) {
@@ -270,27 +291,28 @@ export function useOpenImageMediaEditorModal( { attributes, setAttributes } ) {
270
291
  // has independently customised on the block (i.e. values
271
292
  // that don't match the pre-session attachment metadata)
272
293
  // are left untouched.
294
+ const latestBlockAttributes = blockAttributesRef.current;
273
295
  const resolvedMetadataAttributes =
274
296
  getSyncedImageBlockAttributes(
275
- blockMetadataRef.current,
297
+ latestBlockAttributes,
276
298
  originalAttachment,
277
299
  resolvedAttachment
278
300
  );
279
301
 
280
302
  if ( Object.keys( resolvedMetadataAttributes ).length ) {
281
303
  Object.assign( nextAttributes, resolvedMetadataAttributes );
282
- blockMetadataRef.current = {
283
- ...blockMetadataRef.current,
284
- ...resolvedMetadataAttributes,
285
- };
286
304
  }
287
305
  }
288
306
 
289
307
  if ( Object.keys( nextAttributes ).length ) {
308
+ blockAttributesRef.current = {
309
+ ...blockAttributesRef.current,
310
+ ...nextAttributes,
311
+ };
290
312
  setAttributes( nextAttributes );
291
313
  }
292
314
  },
293
- [ id, resolveFreshAttachmentRecord, setAttributes, url ]
315
+ [ resolveFreshAttachmentRecord, setAttributes ]
294
316
  );
295
317
 
296
318
  const openImageMediaEditorModal = useCallback( async () => {
@@ -306,7 +328,7 @@ export function useOpenImageMediaEditorModal( { attributes, setAttributes } ) {
306
328
  const cachedAttachmentRecord = getCachedAttachmentRecord( id );
307
329
  const fallbackAttachmentRecord =
308
330
  getAttachmentFallbackForEmptyBlockMetadata(
309
- blockMetadataRef.current
331
+ blockAttributesRef.current
310
332
  );
311
333
  const resolvedAttachmentRecord = hasKnownAttachmentMetadata(
312
334
  cachedAttachmentRecord
@@ -324,11 +346,13 @@ export function useOpenImageMediaEditorModal( { attributes, setAttributes } ) {
324
346
  openMediaEditorModal( {
325
347
  id,
326
348
  onUpdate: handleMediaUpdate,
349
+ onClose,
327
350
  } );
328
351
  }, [
329
352
  getCachedAttachmentRecord,
330
353
  handleMediaUpdate,
331
354
  id,
355
+ onClose,
332
356
  openMediaEditorModal,
333
357
  resolveAttachmentRecord,
334
358
  ] );
@@ -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(