@wordpress/block-library 8.15.0 → 8.16.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 (156) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/avatar/index.js +3 -0
  3. package/build/avatar/index.js.map +1 -1
  4. package/build/block/edit.js +2 -30
  5. package/build/block/edit.js.map +1 -1
  6. package/build/cover/index.js +2 -1
  7. package/build/cover/index.js.map +1 -1
  8. package/build/footnotes/edit.js +11 -0
  9. package/build/footnotes/edit.js.map +1 -1
  10. package/build/footnotes/format.js +101 -8
  11. package/build/footnotes/format.js.map +1 -1
  12. package/build/footnotes/index.js +45 -3
  13. package/build/footnotes/index.js.map +1 -1
  14. package/build/gallery/edit.js +7 -5
  15. package/build/gallery/edit.js.map +1 -1
  16. package/build/image/deprecated.js +106 -2
  17. package/build/image/deprecated.js.map +1 -1
  18. package/build/image/image.js +2 -2
  19. package/build/image/image.js.map +1 -1
  20. package/build/image/index.js +2 -1
  21. package/build/image/index.js.map +1 -1
  22. package/build/index.js +3 -1
  23. package/build/index.js.map +1 -1
  24. package/build/list-item/hooks/use-merge.js +10 -1
  25. package/build/list-item/hooks/use-merge.js.map +1 -1
  26. package/build/navigation/edit/menu-inspector-controls.js +1 -1
  27. package/build/navigation/edit/menu-inspector-controls.js.map +1 -1
  28. package/build/navigation/edit/navigation-menu-selector.js +4 -4
  29. package/build/navigation/edit/navigation-menu-selector.js.map +1 -1
  30. package/build/navigation/view-modal.js +93 -32
  31. package/build/navigation/view-modal.js.map +1 -1
  32. package/build/navigation/view.js +63 -31
  33. package/build/navigation/view.js.map +1 -1
  34. package/build/pattern/edit.js +28 -4
  35. package/build/pattern/edit.js.map +1 -1
  36. package/build/preformatted/index.js +4 -0
  37. package/build/preformatted/index.js.map +1 -1
  38. package/build/search/view.js +166 -62
  39. package/build/search/view.js.map +1 -1
  40. package/build/social-link/icons/index.js +13 -0
  41. package/build/social-link/icons/index.js.map +1 -1
  42. package/build/social-link/icons/threads.js +25 -0
  43. package/build/social-link/icons/threads.js.map +1 -0
  44. package/build/social-link/variations.js +7 -0
  45. package/build/social-link/variations.js.map +1 -1
  46. package/build/template-part/edit/import-controls.js +1 -1
  47. package/build/template-part/edit/import-controls.js.map +1 -1
  48. package/build-module/avatar/index.js +3 -0
  49. package/build-module/avatar/index.js.map +1 -1
  50. package/build-module/block/edit.js +4 -29
  51. package/build-module/block/edit.js.map +1 -1
  52. package/build-module/cover/index.js +2 -1
  53. package/build-module/cover/index.js.map +1 -1
  54. package/build-module/footnotes/edit.js +11 -0
  55. package/build-module/footnotes/edit.js.map +1 -1
  56. package/build-module/footnotes/format.js +102 -10
  57. package/build-module/footnotes/format.js.map +1 -1
  58. package/build-module/footnotes/index.js +45 -3
  59. package/build-module/footnotes/index.js.map +1 -1
  60. package/build-module/gallery/edit.js +7 -5
  61. package/build-module/gallery/edit.js.map +1 -1
  62. package/build-module/image/deprecated.js +107 -3
  63. package/build-module/image/deprecated.js.map +1 -1
  64. package/build-module/image/image.js +2 -2
  65. package/build-module/image/image.js.map +1 -1
  66. package/build-module/image/index.js +2 -1
  67. package/build-module/image/index.js.map +1 -1
  68. package/build-module/index.js +3 -1
  69. package/build-module/index.js.map +1 -1
  70. package/build-module/list-item/hooks/use-merge.js +10 -1
  71. package/build-module/list-item/hooks/use-merge.js.map +1 -1
  72. package/build-module/navigation/edit/menu-inspector-controls.js +1 -1
  73. package/build-module/navigation/edit/menu-inspector-controls.js.map +1 -1
  74. package/build-module/navigation/edit/navigation-menu-selector.js +4 -4
  75. package/build-module/navigation/edit/navigation-menu-selector.js.map +1 -1
  76. package/build-module/navigation/view-modal.js +93 -31
  77. package/build-module/navigation/view-modal.js.map +1 -1
  78. package/build-module/navigation/view.js +63 -31
  79. package/build-module/navigation/view.js.map +1 -1
  80. package/build-module/pattern/edit.js +27 -4
  81. package/build-module/pattern/edit.js.map +1 -1
  82. package/build-module/preformatted/index.js +4 -0
  83. package/build-module/preformatted/index.js.map +1 -1
  84. package/build-module/search/view.js +166 -62
  85. package/build-module/search/view.js.map +1 -1
  86. package/build-module/social-link/icons/index.js +1 -0
  87. package/build-module/social-link/icons/index.js.map +1 -1
  88. package/build-module/social-link/icons/threads.js +15 -0
  89. package/build-module/social-link/icons/threads.js.map +1 -0
  90. package/build-module/social-link/variations.js +8 -1
  91. package/build-module/social-link/variations.js.map +1 -1
  92. package/build-module/template-part/edit/import-controls.js +2 -2
  93. package/build-module/template-part/edit/import-controls.js.map +1 -1
  94. package/build-style/preformatted/style-rtl.css +2 -1
  95. package/build-style/preformatted/style.css +2 -1
  96. package/build-style/social-links/style-rtl.css +7 -0
  97. package/build-style/social-links/style.css +7 -0
  98. package/build-style/style-rtl.css +10 -1
  99. package/build-style/style.css +10 -1
  100. package/build-style/video/style-rtl.css +1 -0
  101. package/build-style/video/style.css +1 -0
  102. package/package.json +32 -32
  103. package/src/audio/test/__snapshots__/edit.native.js.snap +60 -0
  104. package/src/avatar/block.json +3 -0
  105. package/src/block/edit.js +1 -39
  106. package/src/buttons/test/edit.native.js +4 -0
  107. package/src/columns/test/edit.native.js +5 -0
  108. package/src/comment-template/index.php +2 -0
  109. package/src/cover/block.json +2 -1
  110. package/src/cover/test/edit.native.js +8 -0
  111. package/src/embed/test/index.native.js +8 -0
  112. package/src/file/index.php +1 -1
  113. package/src/file/test/__snapshots__/edit.native.js.snap +61 -0
  114. package/src/footnotes/block.json +44 -1
  115. package/src/footnotes/edit.js +12 -0
  116. package/src/footnotes/format.js +70 -7
  117. package/src/footnotes/index.js +0 -1
  118. package/src/footnotes/index.php +207 -0
  119. package/src/gallery/edit.js +41 -37
  120. package/src/gallery/test/index.native.js +15 -3
  121. package/src/heading/test/index.native.js +4 -0
  122. package/src/image/block.json +2 -1
  123. package/src/image/deprecated.js +109 -3
  124. package/src/image/image.js +2 -2
  125. package/src/image/index.php +1 -3
  126. package/src/image/test/edit.native.js +0 -1
  127. package/src/index.js +5 -1
  128. package/src/list/test/edit.native.js +5 -0
  129. package/src/list-item/hooks/use-merge.js +12 -5
  130. package/src/missing/test/__snapshots__/edit.native.js.snap +21 -0
  131. package/src/navigation/edit/menu-inspector-controls.js +1 -1
  132. package/src/navigation/edit/navigation-menu-selector.js +8 -4
  133. package/src/navigation/index.php +27 -13
  134. package/src/navigation/view-modal.js +88 -39
  135. package/src/navigation/view.js +69 -36
  136. package/src/paragraph/test/edit.native.js +55 -35
  137. package/src/pattern/edit.js +21 -0
  138. package/src/pattern/index.php +13 -1
  139. package/src/post-template/index.php +2 -0
  140. package/src/post-title/index.php +2 -0
  141. package/src/preformatted/block.json +4 -0
  142. package/src/preformatted/style.scss +4 -1
  143. package/src/pullquote/test/edit.native.js +12 -4
  144. package/src/quote/test/edit.native.js +12 -4
  145. package/src/search/index.php +4 -0
  146. package/src/search/test/__snapshots__/edit.native.js.snap +63 -0
  147. package/src/search/view.js +171 -67
  148. package/src/social-link/icons/index.js +1 -0
  149. package/src/social-link/icons/threads.js +10 -0
  150. package/src/social-link/index.php +4 -0
  151. package/src/social-link/socials-with-bg.scss +5 -0
  152. package/src/social-link/socials-without-bg.scss +4 -0
  153. package/src/social-link/variations.js +7 -0
  154. package/src/template-part/edit/import-controls.js +2 -2
  155. package/src/template-part/index.php +6 -9
  156. package/src/video/style.scss +1 -0
@@ -12,16 +12,24 @@ import { insertObject } from '@wordpress/rich-text';
12
12
  import {
13
13
  RichTextToolbarButton,
14
14
  store as blockEditorStore,
15
+ privateApis,
15
16
  } from '@wordpress/block-editor';
16
17
  import { useSelect, useDispatch, useRegistry } from '@wordpress/data';
17
- import { createBlock } from '@wordpress/blocks';
18
+ import { createBlock, store as blocksStore } from '@wordpress/blocks';
18
19
 
19
20
  /**
20
21
  * Internal dependencies
21
22
  */
22
23
  import { name } from './block.json';
24
+ import { unlock } from '../lock-unlock';
25
+
26
+ const { usesContextKey } = unlock( privateApis );
23
27
 
24
28
  export const formatName = 'core/footnote';
29
+
30
+ const POST_CONTENT_BLOCK_NAME = 'core/post-content';
31
+ const SYNCED_PATTERN_BLOCK_NAME = 'core/block';
32
+
25
33
  export const format = {
26
34
  title: __( 'Footnote' ),
27
35
  tagName: 'sup',
@@ -30,17 +38,56 @@ export const format = {
30
38
  'data-fn': 'data-fn',
31
39
  },
32
40
  contentEditable: false,
33
- edit: function Edit( { value, onChange, isObjectActive } ) {
41
+ [ usesContextKey ]: [ 'postType' ],
42
+ edit: function Edit( {
43
+ value,
44
+ onChange,
45
+ isObjectActive,
46
+ context: { postType },
47
+ } ) {
34
48
  const registry = useRegistry();
35
49
  const {
36
50
  getSelectedBlockClientId,
51
+ getBlocks,
37
52
  getBlockRootClientId,
38
53
  getBlockName,
39
- getBlocks,
54
+ getBlockParentsByBlockName,
40
55
  } = useSelect( blockEditorStore );
56
+ const footnotesBlockType = useSelect( ( select ) =>
57
+ select( blocksStore ).getBlockType( name )
58
+ );
59
+ /*
60
+ * This useSelect exists because we need to use its return value
61
+ * outside the event callback.
62
+ */
63
+ const isBlockWithinPattern = useSelect( ( select ) => {
64
+ const {
65
+ getBlockParentsByBlockName: _getBlockParentsByBlockName,
66
+ getSelectedBlockClientId: _getSelectedBlockClientId,
67
+ } = select( blockEditorStore );
68
+ const parentCoreBlocks = _getBlockParentsByBlockName(
69
+ _getSelectedBlockClientId(),
70
+ SYNCED_PATTERN_BLOCK_NAME
71
+ );
72
+ return parentCoreBlocks && parentCoreBlocks.length > 0;
73
+ }, [] );
74
+
41
75
  const { selectionChange, insertBlock } =
42
76
  useDispatch( blockEditorStore );
43
77
 
78
+ if ( ! footnotesBlockType ) {
79
+ return null;
80
+ }
81
+
82
+ if ( postType !== 'post' && postType !== 'page' ) {
83
+ return null;
84
+ }
85
+
86
+ // Checks if the selected block lives within a pattern.
87
+ if ( isBlockWithinPattern ) {
88
+ return null;
89
+ }
90
+
44
91
  function onClick() {
45
92
  registry.batch( () => {
46
93
  let id;
@@ -65,10 +112,27 @@ export const format = {
65
112
  onChange( newValue );
66
113
  }
67
114
 
115
+ const selectedClientId = getSelectedBlockClientId();
116
+
117
+ /*
118
+ * Attempts to find a common parent post content block.
119
+ * This allows for locating blocks within a page edited in the site editor.
120
+ */
121
+ const parentPostContent = getBlockParentsByBlockName(
122
+ selectedClientId,
123
+ POST_CONTENT_BLOCK_NAME
124
+ );
125
+
126
+ // When called with a post content block, getBlocks will return
127
+ // the block with controlled inner blocks included.
128
+ const blocks = parentPostContent.length
129
+ ? getBlocks( parentPostContent[ 0 ] )
130
+ : getBlocks();
131
+
68
132
  // BFS search to find the first footnote block.
69
133
  let fnBlock = null;
70
134
  {
71
- const queue = [ ...getBlocks() ];
135
+ const queue = [ ...blocks ];
72
136
  while ( queue.length ) {
73
137
  const block = queue.shift();
74
138
  if ( block.name === name ) {
@@ -83,12 +147,11 @@ export const format = {
83
147
  // When there is no footnotes block in the post, create one and
84
148
  // insert it at the bottom.
85
149
  if ( ! fnBlock ) {
86
- const clientId = getSelectedBlockClientId();
87
- let rootClientId = getBlockRootClientId( clientId );
150
+ let rootClientId = getBlockRootClientId( selectedClientId );
88
151
 
89
152
  while (
90
153
  rootClientId &&
91
- getBlockName( rootClientId ) !== 'core/post-content'
154
+ getBlockName( rootClientId ) !== POST_CONTENT_BLOCK_NAME
92
155
  ) {
93
156
  rootClientId = getBlockRootClientId( rootClientId );
94
157
  }
@@ -21,7 +21,6 @@ export const settings = {
21
21
  edit,
22
22
  };
23
23
 
24
- // Would be good to remove the format and HoR if the block is unregistered.
25
24
  registerFormatType( formatName, format );
26
25
 
27
26
  export const init = () => {
@@ -8,6 +8,8 @@
8
8
  /**
9
9
  * Renders the `core/footnotes` block on the server.
10
10
  *
11
+ * @since 6.3.0
12
+ *
11
13
  * @param array $attributes Block attributes.
12
14
  * @param string $content Block default content.
13
15
  * @param WP_Block $block Block instance.
@@ -57,6 +59,8 @@ function render_block_core_footnotes( $attributes, $content, $block ) {
57
59
 
58
60
  /**
59
61
  * Registers the `core/footnotes` block on the server.
62
+ *
63
+ * @since 6.3.0
60
64
  */
61
65
  function register_block_core_footnotes() {
62
66
  foreach ( array( 'post', 'page' ) as $post_type ) {
@@ -78,3 +82,206 @@ function register_block_core_footnotes() {
78
82
  );
79
83
  }
80
84
  add_action( 'init', 'register_block_core_footnotes' );
85
+
86
+ /**
87
+ * Saves the footnotes meta value to the revision.
88
+ *
89
+ * @since 6.3.0
90
+ *
91
+ * @param int $revision_id The revision ID.
92
+ */
93
+ function wp_save_footnotes_meta( $revision_id ) {
94
+ $post_id = wp_is_post_revision( $revision_id );
95
+
96
+ if ( $post_id ) {
97
+ $footnotes = get_post_meta( $post_id, 'footnotes', true );
98
+
99
+ if ( $footnotes ) {
100
+ // Can't use update_post_meta() because it doesn't allow revisions.
101
+ update_metadata( 'post', $revision_id, 'footnotes', $footnotes );
102
+ }
103
+ }
104
+ }
105
+ add_action( 'wp_after_insert_post', 'wp_save_footnotes_meta' );
106
+
107
+ /**
108
+ * Keeps track of the revision ID for "rest_after_insert_{$post_type}".
109
+ *
110
+ * @since 6.3.0
111
+ *
112
+ * @global int $wp_temporary_footnote_revision_id The footnote revision ID.
113
+ *
114
+ * @param int $revision_id The revision ID.
115
+ */
116
+ function wp_keep_footnotes_revision_id( $revision_id ) {
117
+ global $wp_temporary_footnote_revision_id;
118
+ $wp_temporary_footnote_revision_id = $revision_id;
119
+ }
120
+ add_action( '_wp_put_post_revision', 'wp_keep_footnotes_revision_id' );
121
+
122
+ /**
123
+ * This is a specific fix for the REST API. The REST API doesn't update
124
+ * the post and post meta in one go (through `meta_input`). While it
125
+ * does fix the `wp_after_insert_post` hook to be called correctly after
126
+ * updating meta, it does NOT fix hooks such as post_updated and
127
+ * save_post, which are normally also fired after post meta is updated
128
+ * in `wp_insert_post()`. Unfortunately, `wp_save_post_revision` is
129
+ * added to the `post_updated` action, which means the meta is not
130
+ * available at the time, so we have to add it afterwards through the
131
+ * `"rest_after_insert_{$post_type}"` action.
132
+ *
133
+ * @since 6.3.0
134
+ *
135
+ * @global int $wp_temporary_footnote_revision_id The footnote revision ID.
136
+ *
137
+ * @param WP_Post $post The post object.
138
+ */
139
+ function wp_add_footnotes_revisions_to_post_meta( $post ) {
140
+ global $wp_temporary_footnote_revision_id;
141
+
142
+ if ( $wp_temporary_footnote_revision_id ) {
143
+ $revision = get_post( $wp_temporary_footnote_revision_id );
144
+
145
+ if ( ! $revision ) {
146
+ return;
147
+ }
148
+
149
+ $post_id = $revision->post_parent;
150
+
151
+ // Just making sure we're updating the right revision.
152
+ if ( $post->ID === $post_id ) {
153
+ $footnotes = get_post_meta( $post_id, 'footnotes', true );
154
+
155
+ if ( $footnotes ) {
156
+ // Can't use update_post_meta() because it doesn't allow revisions.
157
+ update_metadata( 'post', $wp_temporary_footnote_revision_id, 'footnotes', $footnotes );
158
+ }
159
+ }
160
+ }
161
+ }
162
+
163
+ foreach ( array( 'post', 'page' ) as $post_type ) {
164
+ add_action( "rest_after_insert_{$post_type}", 'wp_add_footnotes_revisions_to_post_meta' );
165
+ }
166
+
167
+ /**
168
+ * Restores the footnotes meta value from the revision.
169
+ *
170
+ * @since 6.3.0
171
+ *
172
+ * @param int $post_id The post ID.
173
+ * @param int $revision_id The revision ID.
174
+ */
175
+ function wp_restore_footnotes_from_revision( $post_id, $revision_id ) {
176
+ $footnotes = get_post_meta( $revision_id, 'footnotes', true );
177
+
178
+ if ( $footnotes ) {
179
+ update_post_meta( $post_id, 'footnotes', $footnotes );
180
+ } else {
181
+ delete_post_meta( $post_id, 'footnotes' );
182
+ }
183
+ }
184
+ add_action( 'wp_restore_post_revision', 'wp_restore_footnotes_from_revision', 10, 2 );
185
+
186
+ /**
187
+ * Adds the footnotes field to the revision.
188
+ *
189
+ * @since 6.3.0
190
+ *
191
+ * @param array $fields The revision fields.
192
+ * @return array The revision fields.
193
+ */
194
+ function wp_add_footnotes_to_revision( $fields ) {
195
+ $fields['footnotes'] = __( 'Footnotes' );
196
+ return $fields;
197
+ }
198
+ add_filter( '_wp_post_revision_fields', 'wp_add_footnotes_to_revision' );
199
+
200
+ /**
201
+ * Gets the footnotes field from the revision.
202
+ *
203
+ * @since 6.3.0
204
+ *
205
+ * @param string $revision_field The field value, but $revision->$field
206
+ * (footnotes) does not exist.
207
+ * @param string $field The field name, in this case "footnotes".
208
+ * @param object $revision The revision object to compare against.
209
+ * @return string The field value.
210
+ */
211
+ function wp_get_footnotes_from_revision( $revision_field, $field, $revision ) {
212
+ return get_metadata( 'post', $revision->ID, $field, true );
213
+ }
214
+ add_filter( '_wp_post_revision_field_footnotes', 'wp_get_footnotes_from_revision', 10, 3 );
215
+
216
+ /**
217
+ * The REST API autosave endpoint doesn't save meta, so we can use the
218
+ * `wp_creating_autosave` when it updates an exiting autosave, and
219
+ * `_wp_put_post_revision` when it creates a new autosave.
220
+ *
221
+ * @since 6.3.0
222
+ *
223
+ * @param int|array $autosave The autosave ID or array.
224
+ */
225
+ function _wp_rest_api_autosave_meta( $autosave ) {
226
+ // Ensure it's a REST API request.
227
+ if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) {
228
+ return;
229
+ }
230
+
231
+ $body = rest_get_server()->get_raw_data();
232
+ $body = json_decode( $body, true );
233
+
234
+ if ( ! isset( $body['meta']['footnotes'] ) ) {
235
+ return;
236
+ }
237
+
238
+ // `wp_creating_autosave` passes the array,
239
+ // `_wp_put_post_revision` passes the ID.
240
+ $id = is_int( $autosave ) ? $autosave : $autosave['ID'];
241
+
242
+ if ( ! $id ) {
243
+ return;
244
+ }
245
+
246
+ update_post_meta( $id, 'footnotes', $body['meta']['footnotes'] );
247
+ }
248
+ // See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L391C1-L391C1.
249
+ add_action( 'wp_creating_autosave', '_wp_rest_api_autosave_meta' );
250
+ // See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L398.
251
+ // Then https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/revision.php#L367.
252
+ add_action( '_wp_put_post_revision', '_wp_rest_api_autosave_meta' );
253
+
254
+ /**
255
+ * This is a workaround for the autosave endpoint returning early if the
256
+ * revision field are equal. The problem is that "footnotes" is not real
257
+ * revision post field, so there's nothing to compare against.
258
+ *
259
+ * This trick sets the "footnotes" field (value doesn't matter), which will
260
+ * cause the autosave endpoint to always update the latest revision. That should
261
+ * be fine, it should be ok to update the revision even if nothing changed. Of
262
+ * course, this is temporary fix.
263
+ *
264
+ * @since 6.3.0
265
+ *
266
+ * @param WP_Post $prepared_post The prepared post object.
267
+ * @param WP_REST_Request $request The request object.
268
+ *
269
+ * See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L365-L384.
270
+ * See https://github.com/WordPress/wordpress-develop/blob/2103cb9966e57d452c94218bbc3171579b536a40/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php#L219.
271
+ */
272
+ function _wp_rest_api_force_autosave_difference( $prepared_post, $request ) {
273
+ // We only want to be altering POST requests.
274
+ if ( $request->get_method() !== 'POST' ) {
275
+ return $prepared_post;
276
+ }
277
+
278
+ // Only alter requests for the '/autosaves' route.
279
+ if ( substr( $request->get_route(), -strlen( '/autosaves' ) ) !== '/autosaves' ) {
280
+ return $prepared_post;
281
+ }
282
+
283
+ $prepared_post->footnotes = '[]';
284
+ return $prepared_post;
285
+ }
286
+
287
+ add_filter( 'rest_pre_insert_post', '_wp_rest_api_force_autosave_difference', 10, 2 );
@@ -91,6 +91,7 @@ function GalleryEdit( props ) {
91
91
  isSelected,
92
92
  insertBlocksAfter,
93
93
  isContentLocked,
94
+ onFocus,
94
95
  } = props;
95
96
 
96
97
  const { columns, imageCrop, linkTarget, linkTo, sizeSlug, caption } =
@@ -498,6 +499,7 @@ function GalleryEdit( props ) {
498
499
  value: hasImageIds ? images : {},
499
500
  autoOpenMediaUpload:
500
501
  ! hasImages && isSelected && wasBlockJustInserted,
502
+ onFocus,
501
503
  },
502
504
  } );
503
505
  const mediaPlaceholder = (
@@ -616,44 +618,46 @@ function GalleryEdit( props ) {
616
618
  ) }
617
619
  </PanelBody>
618
620
  </InspectorControls>
619
- <BlockControls group="block">
620
- { ! isContentLocked && (
621
- <ToolbarButton
622
- onClick={ () => {
623
- setShowCaption( ! showCaption );
624
- if ( showCaption && caption ) {
625
- setAttributes( { caption: undefined } );
626
- }
627
- } }
628
- icon={ captionIcon }
629
- isPressed={ showCaption }
630
- label={
631
- showCaption
632
- ? __( 'Remove caption' )
633
- : __( 'Add caption' )
634
- }
635
- />
636
- ) }
637
- </BlockControls>
638
- <BlockControls group="other">
639
- <MediaReplaceFlow
640
- allowedTypes={ ALLOWED_MEDIA_TYPES }
641
- accept="image/*"
642
- handleUpload={ false }
643
- onSelect={ updateImages }
644
- name={ __( 'Add' ) }
645
- multiple={ true }
646
- mediaIds={ images
647
- .filter( ( image ) => image.id )
648
- .map( ( image ) => image.id ) }
649
- addToGallery={ hasImageIds }
650
- />
651
- </BlockControls>
652
621
  { Platform.isWeb && (
653
- <GapStyles
654
- blockGap={ attributes.style?.spacing?.blockGap }
655
- clientId={ clientId }
656
- />
622
+ <>
623
+ <BlockControls group="block">
624
+ { ! isContentLocked && (
625
+ <ToolbarButton
626
+ onClick={ () => {
627
+ setShowCaption( ! showCaption );
628
+ if ( showCaption && caption ) {
629
+ setAttributes( { caption: undefined } );
630
+ }
631
+ } }
632
+ icon={ captionIcon }
633
+ isPressed={ showCaption }
634
+ label={
635
+ showCaption
636
+ ? __( 'Remove caption' )
637
+ : __( 'Add caption' )
638
+ }
639
+ />
640
+ ) }
641
+ </BlockControls>
642
+ <BlockControls group="other">
643
+ <MediaReplaceFlow
644
+ allowedTypes={ ALLOWED_MEDIA_TYPES }
645
+ accept="image/*"
646
+ handleUpload={ false }
647
+ onSelect={ updateImages }
648
+ name={ __( 'Add' ) }
649
+ multiple={ true }
650
+ mediaIds={ images
651
+ .filter( ( image ) => image.id )
652
+ .map( ( image ) => image.id ) }
653
+ addToGallery={ hasImageIds }
654
+ />
655
+ </BlockControls>
656
+ <GapStyles
657
+ blockGap={ attributes.style?.spacing?.blockGap }
658
+ clientId={ clientId }
659
+ />
660
+ </>
657
661
  ) }
658
662
  <Gallery
659
663
  { ...props }
@@ -159,9 +159,7 @@ describe( 'Gallery block', () => {
159
159
  /* eslint-enable jest/no-conditional-expect */
160
160
  } );
161
161
 
162
- // This case is disabled until the issue (https://github.com/WordPress/gutenberg/issues/38444)
163
- // is addressed.
164
- it.skip( 'block remains selected after dismissing the media options picker', async () => {
162
+ it( 'block remains selected after dismissing the media options picker', async () => {
165
163
  // Initialize with an empty gallery
166
164
  const { getByLabelText, getByText, getByTestId } =
167
165
  await initializeEditor( {
@@ -622,6 +620,20 @@ describe( 'Gallery block', () => {
622
620
  expect( getEditorHtml() ).toMatchSnapshot();
623
621
  } );
624
622
 
623
+ it( 'does not display MediaReplaceFlow component within the block toolbar', async () => {
624
+ const screen = await initializeWithGalleryBlock( {
625
+ numberOfItems: 3,
626
+ media,
627
+ } );
628
+ const { queryByTestId } = screen;
629
+
630
+ fireEvent.press( getBlock( screen, 'Gallery' ) );
631
+
632
+ // Expect the native MediaReplaceFlow component to not be present in the block toolbar
633
+ const mediaReplaceFlow = queryByTestId( 'media-replace-flow' );
634
+ expect( mediaReplaceFlow ).toBeNull();
635
+ } );
636
+
625
637
  // Test cases related to TC013 - Settings - Columns
626
638
  // Reference: https://github.com/wordpress-mobile/test-cases/blob/trunk/test-cases/gutenberg/gallery.md#tc013
627
639
  describe( 'Columns setting', () => {
@@ -71,6 +71,10 @@ describe( 'Heading block', () => {
71
71
 
72
72
  // Tap one color
73
73
  fireEvent.press( screen.getByLabelText( 'Pale pink' ) );
74
+ // TODO(jest-console): Fix the warning and remove the expect below.
75
+ expect( console ).toHaveWarnedWith(
76
+ `Non-serializable values were found in the navigation state. Check:\n\nColor > params.onColorChange (Function)\n\nThis can break usage such as persisting and restoring state. This might happen if you passed non-serializable values such as function, class instances etc. in params. If you need to use components with callbacks in your options, you can use 'navigation.setOptions' instead. See https://reactnavigation.org/docs/troubleshooting#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state for more details.`
77
+ );
74
78
 
75
79
  // Dismiss the Block Settings modal.
76
80
  fireEvent( blockSettingsModal, 'backdropPress' );
@@ -127,5 +127,6 @@
127
127
  { "name": "rounded", "label": "Rounded" }
128
128
  ],
129
129
  "editorStyle": "wp-block-image-editor",
130
- "style": "wp-block-image"
130
+ "style": "wp-block-image",
131
+ "viewScript": "file:./view-interactivity.min.js"
131
132
  }
@@ -9,7 +9,8 @@ import classnames from 'classnames';
9
9
  import {
10
10
  RichText,
11
11
  useBlockProps,
12
- __experimentalGetElementClassName as getBorderClassesAndStyles,
12
+ __experimentalGetElementClassName,
13
+ __experimentalGetBorderClassesAndStyles as getBorderClassesAndStyles,
13
14
  } from '@wordpress/block-editor';
14
15
 
15
16
  /**
@@ -545,11 +546,114 @@ const v5 = {
545
546
 
546
547
  /**
547
548
  * Deprecation for adding width and height as style rules on the inner img.
548
- * It also updates the widht and height attributes to be strings instead of numbers.
549
549
  *
550
550
  * @see https://github.com/WordPress/gutenberg/pull/31366
551
551
  */
552
552
  const v6 = {
553
+ attributes: {
554
+ align: {
555
+ type: 'string',
556
+ },
557
+ url: {
558
+ type: 'string',
559
+ source: 'attribute',
560
+ selector: 'img',
561
+ attribute: 'src',
562
+ __experimentalRole: 'content',
563
+ },
564
+ alt: {
565
+ type: 'string',
566
+ source: 'attribute',
567
+ selector: 'img',
568
+ attribute: 'alt',
569
+ default: '',
570
+ __experimentalRole: 'content',
571
+ },
572
+ caption: {
573
+ type: 'string',
574
+ source: 'html',
575
+ selector: 'figcaption',
576
+ __experimentalRole: 'content',
577
+ },
578
+ title: {
579
+ type: 'string',
580
+ source: 'attribute',
581
+ selector: 'img',
582
+ attribute: 'title',
583
+ __experimentalRole: 'content',
584
+ },
585
+ href: {
586
+ type: 'string',
587
+ source: 'attribute',
588
+ selector: 'figure > a',
589
+ attribute: 'href',
590
+ __experimentalRole: 'content',
591
+ },
592
+ rel: {
593
+ type: 'string',
594
+ source: 'attribute',
595
+ selector: 'figure > a',
596
+ attribute: 'rel',
597
+ },
598
+ linkClass: {
599
+ type: 'string',
600
+ source: 'attribute',
601
+ selector: 'figure > a',
602
+ attribute: 'class',
603
+ },
604
+ id: {
605
+ type: 'number',
606
+ __experimentalRole: 'content',
607
+ },
608
+ width: {
609
+ type: 'number',
610
+ },
611
+ height: {
612
+ type: 'number',
613
+ },
614
+ aspectRatio: {
615
+ type: 'string',
616
+ },
617
+ scale: {
618
+ type: 'string',
619
+ },
620
+ sizeSlug: {
621
+ type: 'string',
622
+ },
623
+ linkDestination: {
624
+ type: 'string',
625
+ },
626
+ linkTarget: {
627
+ type: 'string',
628
+ source: 'attribute',
629
+ selector: 'figure > a',
630
+ attribute: 'target',
631
+ },
632
+ },
633
+ supports: {
634
+ anchor: true,
635
+ behaviors: {
636
+ lightbox: true,
637
+ },
638
+ color: {
639
+ text: false,
640
+ background: false,
641
+ },
642
+ filter: {
643
+ duotone: true,
644
+ },
645
+ __experimentalBorder: {
646
+ color: true,
647
+ radius: true,
648
+ width: true,
649
+ __experimentalSkipSerialization: true,
650
+ __experimentalDefaultControls: {
651
+ color: true,
652
+ radius: true,
653
+ width: true,
654
+ },
655
+ },
656
+ },
553
657
  save( { attributes } ) {
554
658
  const {
555
659
  url,
@@ -618,7 +722,9 @@ const v6 = {
618
722
  ) }
619
723
  { ! RichText.isEmpty( caption ) && (
620
724
  <RichText.Content
621
- className={ getBorderClassesAndStyles( 'caption' ) }
725
+ className={ __experimentalGetElementClassName(
726
+ 'caption'
727
+ ) }
622
728
  tagName="figcaption"
623
729
  value={ caption }
624
730
  />
@@ -566,9 +566,9 @@ export default function Image( {
566
566
  className={ borderProps.className }
567
567
  style={ {
568
568
  width:
569
- ( width && height ) || aspectRatio ? '100%' : 'inherit',
569
+ ( width && height ) || aspectRatio ? '100%' : undefined,
570
570
  height:
571
- ( width && height ) || aspectRatio ? '100%' : 'inherit',
571
+ ( width && height ) || aspectRatio ? '100%' : undefined,
572
572
  objectFit: scale,
573
573
  ...borderProps.style,
574
574
  } }
@@ -32,7 +32,6 @@ function render_block_core_image( $attributes, $content, $block ) {
32
32
  }
33
33
 
34
34
  $should_load_view_script = false;
35
- $experiments = get_option( 'gutenberg-experiments' );
36
35
  $link_destination = isset( $attributes['linkDestination'] ) ? $attributes['linkDestination'] : 'none';
37
36
  // Get the lightbox setting from the block attributes.
38
37
  if ( isset( $attributes['behaviors']['lightbox'] ) ) {
@@ -50,8 +49,7 @@ function render_block_core_image( $attributes, $content, $block ) {
50
49
  // If the lightbox is enabled, the image is not linked, and the Interactivity API is enabled, load the view script.
51
50
  if ( isset( $lightbox_settings['enabled'] ) &&
52
51
  true === $lightbox_settings['enabled'] &&
53
- 'none' === $link_destination &&
54
- ! empty( $experiments['gutenberg-interactivity-api-core-blocks'] )
52
+ 'none' === $link_destination
55
53
  ) {
56
54
  $should_load_view_script = true;
57
55
  }
@@ -26,7 +26,6 @@ import { select, dispatch } from '@wordpress/data';
26
26
  import { store as editorStore } from '@wordpress/editor';
27
27
  import { store as coreStore } from '@wordpress/core-data';
28
28
  import apiFetch from '@wordpress/api-fetch';
29
- import '@wordpress/jest-console';
30
29
 
31
30
  /**
32
31
  * Internal dependencies