@wordpress/block-library 8.24.1 → 8.25.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 (327) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/audio/edit.js +7 -52
  3. package/build/audio/edit.js.map +1 -1
  4. package/build/audio/edit.native.js +1 -1
  5. package/build/audio/edit.native.js.map +1 -1
  6. package/build/audio/index.js +2 -2
  7. package/build/block/edit.js +102 -11
  8. package/build/block/edit.js.map +1 -1
  9. package/build/block/index.js +3 -2
  10. package/build/block/index.js.map +1 -1
  11. package/build/block/v1/edit.js +116 -0
  12. package/build/block/v1/edit.js.map +1 -0
  13. package/build/block/{edit.native.js → v1/edit.native.js} +2 -2
  14. package/build/block/v1/edit.native.js.map +1 -0
  15. package/build/button/index.js +2 -2
  16. package/build/button/save.js +1 -1
  17. package/build/button/save.js.map +1 -1
  18. package/build/code/edit.native.js +13 -14
  19. package/build/code/edit.native.js.map +1 -1
  20. package/build/code/index.js +2 -2
  21. package/build/code/save.js +6 -2
  22. package/build/code/save.js.map +1 -1
  23. package/build/details/index.js +2 -2
  24. package/build/embed/deprecated.js +2 -2
  25. package/build/embed/embed-preview.native.js +1 -1
  26. package/build/embed/embed-preview.native.js.map +1 -1
  27. package/build/embed/icons.js +1 -1
  28. package/build/embed/icons.js.map +1 -1
  29. package/build/embed/index.js +2 -2
  30. package/build/embed/transforms.js +2 -2
  31. package/build/embed/util.js +2 -2
  32. package/build/file/edit.js +1 -1
  33. package/build/file/edit.js.map +1 -1
  34. package/build/file/edit.native.js +1 -1
  35. package/build/file/edit.native.js.map +1 -1
  36. package/build/file/index.js +4 -4
  37. package/build/file/save.js +4 -1
  38. package/build/file/save.js.map +1 -1
  39. package/build/form-input/deprecated.js +147 -0
  40. package/build/form-input/deprecated.js.map +1 -0
  41. package/build/form-input/edit.js +1 -1
  42. package/build/form-input/edit.js.map +1 -1
  43. package/build/form-input/index.js +4 -2
  44. package/build/form-input/index.js.map +1 -1
  45. package/build/form-input/save.js +7 -9
  46. package/build/form-input/save.js.map +1 -1
  47. package/build/gallery/edit.js +2 -41
  48. package/build/gallery/edit.js.map +1 -1
  49. package/build/gallery/gallery.js +18 -24
  50. package/build/gallery/gallery.js.map +1 -1
  51. package/build/gallery/gallery.native.js +1 -1
  52. package/build/gallery/gallery.native.js.map +1 -1
  53. package/build/gallery/index.js +4 -4
  54. package/build/gallery/v1/gallery.native.js +1 -1
  55. package/build/gallery/v1/gallery.native.js.map +1 -1
  56. package/build/group/edit.js +6 -1
  57. package/build/group/edit.js.map +1 -1
  58. package/build/heading/index.js +3 -6
  59. package/build/heading/index.js.map +1 -1
  60. package/build/image/edit.native.js +1 -1
  61. package/build/image/edit.native.js.map +1 -1
  62. package/build/image/image.js +14 -51
  63. package/build/image/image.js.map +1 -1
  64. package/build/image/index.js +2 -2
  65. package/build/image/save.js +3 -1
  66. package/build/image/save.js.map +1 -1
  67. package/build/list-item/index.js +10 -3
  68. package/build/list-item/index.js.map +1 -1
  69. package/build/navigation/edit/index.js +1 -1
  70. package/build/navigation/edit/index.js.map +1 -1
  71. package/build/page-list/convert-to-links-modal.js +2 -2
  72. package/build/page-list/convert-to-links-modal.js.map +1 -1
  73. package/build/paragraph/edit.js +54 -32
  74. package/build/paragraph/edit.js.map +1 -1
  75. package/build/paragraph/index.js +2 -3
  76. package/build/paragraph/index.js.map +1 -1
  77. package/build/paragraph/transforms.js +2 -3
  78. package/build/paragraph/transforms.js.map +1 -1
  79. package/build/post-title/index.js +1 -3
  80. package/build/post-title/index.js.map +1 -1
  81. package/build/preformatted/index.js +2 -3
  82. package/build/preformatted/index.js.map +1 -1
  83. package/build/pullquote/index.js +5 -7
  84. package/build/pullquote/index.js.map +1 -1
  85. package/build/query/edit/inspector-controls/taxonomy-controls.js +2 -0
  86. package/build/query/edit/inspector-controls/taxonomy-controls.js.map +1 -1
  87. package/build/query-title/index.js +1 -3
  88. package/build/query-title/index.js.map +1 -1
  89. package/build/quote/index.js +3 -5
  90. package/build/quote/index.js.map +1 -1
  91. package/build/quote/transforms.js +7 -6
  92. package/build/quote/transforms.js.map +1 -1
  93. package/build/site-title/index.js +1 -5
  94. package/build/site-title/index.js.map +1 -1
  95. package/build/social-link/icons/gravatar.js +22 -0
  96. package/build/social-link/icons/gravatar.js.map +1 -0
  97. package/build/social-link/icons/index.js +11 -0
  98. package/build/social-link/icons/index.js.map +1 -1
  99. package/build/social-link/variations.js +7 -0
  100. package/build/social-link/variations.js.map +1 -1
  101. package/build/table/edit.js +3 -1
  102. package/build/table/edit.js.map +1 -1
  103. package/build/table/index.js +9 -10
  104. package/build/table/index.js.map +1 -1
  105. package/build/table-of-contents/edit.js +2 -2
  106. package/build/table-of-contents/edit.js.map +1 -1
  107. package/build/table-of-contents/index.js +5 -2
  108. package/build/table-of-contents/index.js.map +1 -1
  109. package/build/utils/caption.js +90 -0
  110. package/build/utils/caption.js.map +1 -0
  111. package/build/utils/remove-anchor-tag.js +2 -1
  112. package/build/utils/remove-anchor-tag.js.map +1 -1
  113. package/build/verse/index.js +3 -5
  114. package/build/verse/index.js.map +1 -1
  115. package/build/video/deprecated.js +2 -2
  116. package/build/video/edit.js +8 -52
  117. package/build/video/edit.js.map +1 -1
  118. package/build/video/edit.native.js +1 -1
  119. package/build/video/edit.native.js.map +1 -1
  120. package/build/video/index.js +2 -2
  121. package/build-module/audio/edit.js +11 -56
  122. package/build-module/audio/edit.js.map +1 -1
  123. package/build-module/audio/edit.native.js +2 -2
  124. package/build-module/audio/edit.native.js.map +1 -1
  125. package/build-module/audio/index.js +2 -2
  126. package/build-module/block/edit.js +104 -13
  127. package/build-module/block/edit.js.map +1 -1
  128. package/build-module/block/index.js +3 -2
  129. package/build-module/block/index.js.map +1 -1
  130. package/build-module/block/v1/edit.js +108 -0
  131. package/build-module/block/v1/edit.js.map +1 -0
  132. package/build-module/block/{edit.native.js → v1/edit.native.js} +2 -2
  133. package/build-module/block/v1/edit.native.js.map +1 -0
  134. package/build-module/button/index.js +2 -2
  135. package/build-module/button/save.js +1 -1
  136. package/build-module/button/save.js.map +1 -1
  137. package/build-module/code/edit.native.js +14 -16
  138. package/build-module/code/edit.native.js.map +1 -1
  139. package/build-module/code/index.js +2 -2
  140. package/build-module/code/save.js +6 -2
  141. package/build-module/code/save.js.map +1 -1
  142. package/build-module/details/index.js +2 -2
  143. package/build-module/embed/deprecated.js +2 -2
  144. package/build-module/embed/embed-preview.native.js +2 -2
  145. package/build-module/embed/embed-preview.native.js.map +1 -1
  146. package/build-module/embed/icons.js +1 -1
  147. package/build-module/embed/icons.js.map +1 -1
  148. package/build-module/embed/index.js +2 -2
  149. package/build-module/embed/transforms.js +2 -2
  150. package/build-module/embed/util.js +2 -2
  151. package/build-module/file/edit.js +1 -1
  152. package/build-module/file/edit.js.map +1 -1
  153. package/build-module/file/edit.native.js +1 -1
  154. package/build-module/file/edit.native.js.map +1 -1
  155. package/build-module/file/index.js +4 -4
  156. package/build-module/file/save.js +4 -1
  157. package/build-module/file/save.js.map +1 -1
  158. package/build-module/form-input/deprecated.js +138 -0
  159. package/build-module/form-input/deprecated.js.map +1 -0
  160. package/build-module/form-input/edit.js +1 -1
  161. package/build-module/form-input/edit.js.map +1 -1
  162. package/build-module/form-input/index.js +4 -2
  163. package/build-module/form-input/index.js.map +1 -1
  164. package/build-module/form-input/save.js +8 -10
  165. package/build-module/form-input/save.js.map +1 -1
  166. package/build-module/gallery/edit.js +5 -44
  167. package/build-module/gallery/edit.js.map +1 -1
  168. package/build-module/gallery/gallery.js +17 -21
  169. package/build-module/gallery/gallery.js.map +1 -1
  170. package/build-module/gallery/gallery.native.js +2 -2
  171. package/build-module/gallery/gallery.native.js.map +1 -1
  172. package/build-module/gallery/index.js +4 -4
  173. package/build-module/gallery/v1/gallery.native.js +2 -2
  174. package/build-module/gallery/v1/gallery.native.js.map +1 -1
  175. package/build-module/group/edit.js +6 -1
  176. package/build-module/group/edit.js.map +1 -1
  177. package/build-module/heading/index.js +3 -6
  178. package/build-module/heading/index.js.map +1 -1
  179. package/build-module/image/edit.native.js +2 -2
  180. package/build-module/image/edit.native.js.map +1 -1
  181. package/build-module/image/image.js +19 -56
  182. package/build-module/image/image.js.map +1 -1
  183. package/build-module/image/index.js +2 -2
  184. package/build-module/image/save.js +3 -1
  185. package/build-module/image/save.js.map +1 -1
  186. package/build-module/list-item/index.js +10 -3
  187. package/build-module/list-item/index.js.map +1 -1
  188. package/build-module/navigation/edit/index.js +2 -2
  189. package/build-module/navigation/edit/index.js.map +1 -1
  190. package/build-module/page-list/convert-to-links-modal.js +2 -2
  191. package/build-module/page-list/convert-to-links-modal.js.map +1 -1
  192. package/build-module/paragraph/edit.js +54 -32
  193. package/build-module/paragraph/edit.js.map +1 -1
  194. package/build-module/paragraph/index.js +2 -3
  195. package/build-module/paragraph/index.js.map +1 -1
  196. package/build-module/paragraph/transforms.js +2 -3
  197. package/build-module/paragraph/transforms.js.map +1 -1
  198. package/build-module/post-title/index.js +1 -3
  199. package/build-module/post-title/index.js.map +1 -1
  200. package/build-module/preformatted/index.js +2 -3
  201. package/build-module/preformatted/index.js.map +1 -1
  202. package/build-module/pullquote/index.js +5 -7
  203. package/build-module/pullquote/index.js.map +1 -1
  204. package/build-module/query/edit/inspector-controls/taxonomy-controls.js +2 -0
  205. package/build-module/query/edit/inspector-controls/taxonomy-controls.js.map +1 -1
  206. package/build-module/query-title/index.js +1 -3
  207. package/build-module/query-title/index.js.map +1 -1
  208. package/build-module/quote/index.js +3 -5
  209. package/build-module/quote/index.js.map +1 -1
  210. package/build-module/quote/transforms.js +7 -6
  211. package/build-module/quote/transforms.js.map +1 -1
  212. package/build-module/site-title/index.js +1 -5
  213. package/build-module/site-title/index.js.map +1 -1
  214. package/build-module/social-link/icons/gravatar.js +14 -0
  215. package/build-module/social-link/icons/gravatar.js.map +1 -0
  216. package/build-module/social-link/icons/index.js +1 -0
  217. package/build-module/social-link/icons/index.js.map +1 -1
  218. package/build-module/social-link/variations.js +8 -1
  219. package/build-module/social-link/variations.js.map +1 -1
  220. package/build-module/table/edit.js +3 -1
  221. package/build-module/table/edit.js.map +1 -1
  222. package/build-module/table/index.js +9 -10
  223. package/build-module/table/index.js.map +1 -1
  224. package/build-module/table-of-contents/edit.js +1 -1
  225. package/build-module/table-of-contents/edit.js.map +1 -1
  226. package/build-module/table-of-contents/index.js +5 -1
  227. package/build-module/table-of-contents/index.js.map +1 -1
  228. package/build-module/utils/caption.js +82 -0
  229. package/build-module/utils/caption.js.map +1 -0
  230. package/build-module/utils/remove-anchor-tag.js +2 -1
  231. package/build-module/utils/remove-anchor-tag.js.map +1 -1
  232. package/build-module/verse/index.js +3 -5
  233. package/build-module/verse/index.js.map +1 -1
  234. package/build-module/video/deprecated.js +2 -2
  235. package/build-module/video/edit.js +13 -57
  236. package/build-module/video/edit.js.map +1 -1
  237. package/build-module/video/edit.native.js +2 -2
  238. package/build-module/video/edit.native.js.map +1 -1
  239. package/build-module/video/index.js +2 -2
  240. package/build-style/editor-rtl.css +1 -11
  241. package/build-style/editor.css +1 -11
  242. package/build-style/social-links/style-rtl.css +14 -2
  243. package/build-style/social-links/style.css +14 -2
  244. package/build-style/style-rtl.css +14 -2
  245. package/build-style/style.css +14 -2
  246. package/build-style/table/editor-rtl.css +1 -11
  247. package/build-style/table/editor.css +1 -11
  248. package/package.json +32 -32
  249. package/src/audio/block.json +2 -2
  250. package/src/audio/edit.js +11 -74
  251. package/src/audio/edit.native.js +2 -1
  252. package/src/block/edit.js +143 -16
  253. package/src/block/index.js +3 -2
  254. package/src/block/index.php +48 -0
  255. package/src/block/v1/edit.js +163 -0
  256. package/src/block/{edit.native.js → v1/edit.native.js} +2 -2
  257. package/src/button/block.json +2 -2
  258. package/src/button/save.js +1 -1
  259. package/src/code/block.json +2 -2
  260. package/src/code/edit.native.js +11 -13
  261. package/src/code/save.js +4 -1
  262. package/src/code/test/edit.native.js +2 -2
  263. package/src/cover/test/edit.native.js +7 -1
  264. package/src/details/block.json +2 -2
  265. package/src/embed/block.json +2 -2
  266. package/src/embed/embed-preview.native.js +2 -1
  267. package/src/embed/icons.js +1 -1
  268. package/src/file/block.json +4 -4
  269. package/src/file/edit.js +1 -1
  270. package/src/file/edit.native.js +1 -1
  271. package/src/file/save.js +5 -1
  272. package/src/form-input/block.json +2 -2
  273. package/src/form-input/deprecated.js +142 -0
  274. package/src/form-input/edit.js +1 -1
  275. package/src/form-input/index.js +2 -0
  276. package/src/form-input/save.js +27 -24
  277. package/src/gallery/block.json +4 -4
  278. package/src/gallery/edit.js +4 -59
  279. package/src/gallery/gallery.js +19 -36
  280. package/src/gallery/gallery.native.js +6 -2
  281. package/src/gallery/v1/gallery.native.js +2 -1
  282. package/src/group/edit.js +4 -1
  283. package/src/heading/block.json +3 -6
  284. package/src/image/block.json +2 -2
  285. package/src/image/edit.native.js +2 -3
  286. package/src/image/image.js +24 -93
  287. package/src/image/save.js +3 -1
  288. package/src/list-item/block.json +10 -3
  289. package/src/navigation/edit/index.js +7 -2
  290. package/src/navigation/index.php +1 -1
  291. package/src/navigation-link/test/__snapshots__/hooks.js.snap +6 -3
  292. package/src/page-list/convert-to-links-modal.js +2 -2
  293. package/src/paragraph/block.json +2 -3
  294. package/src/paragraph/edit.js +53 -40
  295. package/src/post-title/block.json +1 -3
  296. package/src/preformatted/block.json +2 -3
  297. package/src/pullquote/block.json +5 -7
  298. package/src/query/edit/inspector-controls/taxonomy-controls.js +2 -0
  299. package/src/query-title/block.json +1 -3
  300. package/src/quote/block.json +3 -5
  301. package/src/quote/transforms.js +12 -11
  302. package/src/site-title/block.json +1 -5
  303. package/src/social-link/icons/gravatar.js +10 -0
  304. package/src/social-link/icons/index.js +1 -0
  305. package/src/social-link/index.php +4 -0
  306. package/src/social-link/socials-with-bg.scss +5 -0
  307. package/src/social-link/socials-without-bg.scss +4 -0
  308. package/src/social-link/variations.js +7 -0
  309. package/src/social-links/style.scss +14 -8
  310. package/src/table/block.json +9 -10
  311. package/src/table/edit.js +3 -1
  312. package/src/table/editor.scss +1 -14
  313. package/src/table-of-contents/edit.js +1 -1
  314. package/src/table-of-contents/index.js +5 -1
  315. package/src/utils/caption.js +108 -0
  316. package/src/utils/remove-anchor-tag.js +2 -1
  317. package/src/verse/block.json +3 -5
  318. package/src/video/block.json +2 -2
  319. package/src/video/edit.js +12 -74
  320. package/src/video/edit.native.js +2 -1
  321. package/build/block/edit.native.js.map +0 -1
  322. package/build/table-of-contents/icon.js +0 -22
  323. package/build/table-of-contents/icon.js.map +0 -1
  324. package/build-module/block/edit.native.js.map +0 -1
  325. package/build-module/table-of-contents/icon.js +0 -15
  326. package/build-module/table-of-contents/icon.js.map +0 -1
  327. package/src/table-of-contents/icon.js +0 -18
package/src/audio/edit.js CHANGED
@@ -13,7 +13,6 @@ import {
13
13
  SelectControl,
14
14
  Spinner,
15
15
  ToggleControl,
16
- ToolbarButton,
17
16
  } from '@wordpress/components';
18
17
  import {
19
18
  BlockControls,
@@ -21,23 +20,20 @@ import {
21
20
  InspectorControls,
22
21
  MediaPlaceholder,
23
22
  MediaReplaceFlow,
24
- RichText,
25
23
  useBlockProps,
26
24
  store as blockEditorStore,
27
- __experimentalGetElementClassName,
28
25
  } from '@wordpress/block-editor';
29
- import { useEffect, useState, useCallback } from '@wordpress/element';
26
+ import { useEffect } from '@wordpress/element';
30
27
  import { __, _x } from '@wordpress/i18n';
31
28
  import { useDispatch, useSelect } from '@wordpress/data';
32
- import { audio as icon, caption as captionIcon } from '@wordpress/icons';
33
- import { createBlock, getDefaultBlockName } from '@wordpress/blocks';
29
+ import { audio as icon } from '@wordpress/icons';
34
30
  import { store as noticesStore } from '@wordpress/notices';
35
- import { usePrevious } from '@wordpress/compose';
36
31
 
37
32
  /**
38
33
  * Internal dependencies
39
34
  */
40
35
  import { createUpgradedEmbedBlock } from '../embed/util';
36
+ import { Caption } from '../utils/caption';
41
37
 
42
38
  const ALLOWED_MEDIA_TYPES = [ 'audio' ];
43
39
 
@@ -49,9 +45,7 @@ function AudioEdit( {
49
45
  isSelected,
50
46
  insertBlocksAfter,
51
47
  } ) {
52
- const { id, autoplay, caption, loop, preload, src } = attributes;
53
- const prevCaption = usePrevious( caption );
54
- const [ showCaption, setShowCaption ] = useState( !! caption );
48
+ const { id, autoplay, loop, preload, src } = attributes;
55
49
  const isTemporaryAudio = ! id && isBlobURL( src );
56
50
  const mediaUpload = useSelect( ( select ) => {
57
51
  const { getSettings } = select( blockEditorStore );
@@ -73,30 +67,6 @@ function AudioEdit( {
73
67
  }
74
68
  }, [] );
75
69
 
76
- // We need to show the caption when changes come from
77
- // history navigation(undo/redo).
78
- useEffect( () => {
79
- if ( caption && ! prevCaption ) {
80
- setShowCaption( true );
81
- }
82
- }, [ caption, prevCaption ] );
83
-
84
- // Focus the caption when we click to add one.
85
- const captionRef = useCallback(
86
- ( node ) => {
87
- if ( node && ! caption ) {
88
- node.focus();
89
- }
90
- },
91
- [ caption ]
92
- );
93
-
94
- useEffect( () => {
95
- if ( ! isSelected && ! caption ) {
96
- setShowCaption( false );
97
- }
98
- }, [ isSelected, caption ] );
99
-
100
70
  function toggleAttribute( attribute ) {
101
71
  return ( newValue ) => {
102
72
  setAttributes( { [ attribute ]: newValue } );
@@ -176,23 +146,6 @@ function AudioEdit( {
176
146
 
177
147
  return (
178
148
  <>
179
- <BlockControls group="block">
180
- <ToolbarButton
181
- onClick={ () => {
182
- setShowCaption( ! showCaption );
183
- if ( showCaption && caption ) {
184
- setAttributes( { caption: undefined } );
185
- }
186
- } }
187
- icon={ captionIcon }
188
- isPressed={ showCaption }
189
- label={
190
- showCaption
191
- ? __( 'Remove caption' )
192
- : __( 'Add caption' )
193
- }
194
- />
195
- </BlockControls>
196
149
  <BlockControls group="other">
197
150
  <MediaReplaceFlow
198
151
  mediaId={ id }
@@ -251,29 +204,13 @@ function AudioEdit( {
251
204
  <audio controls="controls" src={ src } />
252
205
  </Disabled>
253
206
  { isTemporaryAudio && <Spinner /> }
254
- { showCaption &&
255
- ( ! RichText.isEmpty( caption ) || isSelected ) && (
256
- <RichText
257
- identifier="caption"
258
- tagName="figcaption"
259
- className={ __experimentalGetElementClassName(
260
- 'caption'
261
- ) }
262
- ref={ captionRef }
263
- aria-label={ __( 'Audio caption text' ) }
264
- placeholder={ __( 'Add caption' ) }
265
- value={ caption }
266
- onChange={ ( value ) =>
267
- setAttributes( { caption: value } )
268
- }
269
- inlineToolbar
270
- __unstableOnSplitAtEnd={ () =>
271
- insertBlocksAfter(
272
- createBlock( getDefaultBlockName() )
273
- )
274
- }
275
- />
276
- ) }
207
+ <Caption
208
+ attributes={ attributes }
209
+ setAttributes={ setAttributes }
210
+ isSelected={ isSelected }
211
+ insertBlocksAfter={ insertBlocksAfter }
212
+ label={ __( 'Audio caption text' ) }
213
+ />
277
214
  </figure>
278
215
  </>
279
216
  );
@@ -23,6 +23,7 @@ import {
23
23
  MediaPlaceholder,
24
24
  MediaUpload,
25
25
  MediaUploadProgress,
26
+ RichText,
26
27
  store as blockEditorStore,
27
28
  } from '@wordpress/block-editor';
28
29
  import { __, _x, sprintf } from '@wordpress/i18n';
@@ -227,7 +228,7 @@ function AudioEdit( {
227
228
  <BlockCaption
228
229
  accessible={ true }
229
230
  accessibilityLabelCreator={ ( caption ) =>
230
- ! caption
231
+ RichText.isEmpty( caption )
231
232
  ? /* translators: accessibility text. Empty Audio caption. */
232
233
  __( 'Audio caption. Empty' )
233
234
  : sprintf(
package/src/block/edit.js CHANGED
@@ -6,11 +6,9 @@ import classnames from 'classnames';
6
6
  /**
7
7
  * WordPress dependencies
8
8
  */
9
- import {
10
- useEntityBlockEditor,
11
- useEntityProp,
12
- useEntityRecord,
13
- } from '@wordpress/core-data';
9
+ import { useRegistry, useSelect, useDispatch } from '@wordpress/data';
10
+ import { useRef, useMemo, useEffect } from '@wordpress/element';
11
+ import { useEntityProp, useEntityRecord } from '@wordpress/core-data';
14
12
  import {
15
13
  Placeholder,
16
14
  Spinner,
@@ -27,8 +25,9 @@ import {
27
25
  useBlockProps,
28
26
  Warning,
29
27
  privateApis as blockEditorPrivateApis,
28
+ store as blockEditorStore,
30
29
  } from '@wordpress/block-editor';
31
- import { useRef, useMemo } from '@wordpress/element';
30
+ import { getBlockSupport, parse } from '@wordpress/blocks';
32
31
 
33
32
  /**
34
33
  * Internal dependencies
@@ -36,6 +35,24 @@ import { useRef, useMemo } from '@wordpress/element';
36
35
  import { unlock } from '../lock-unlock';
37
36
 
38
37
  const { useLayoutClasses } = unlock( blockEditorPrivateApis );
38
+
39
+ function isPartiallySynced( block ) {
40
+ return (
41
+ !! getBlockSupport( block.name, '__experimentalConnections', false ) &&
42
+ !! block.attributes.connections?.attributes &&
43
+ Object.values( block.attributes.connections.attributes ).some(
44
+ ( connection ) => connection.source === 'pattern_attributes'
45
+ )
46
+ );
47
+ }
48
+ function getPartiallySyncedAttributes( block ) {
49
+ return Object.entries( block.attributes.connections.attributes )
50
+ .filter(
51
+ ( [ , connection ] ) => connection.source === 'pattern_attributes'
52
+ )
53
+ .map( ( [ attributeKey ] ) => attributeKey );
54
+ }
55
+
39
56
  const fullAlignments = [ 'full', 'wide', 'left', 'right' ];
40
57
 
41
58
  const useInferredLayout = ( blocks, parentLayout ) => {
@@ -67,11 +84,61 @@ const useInferredLayout = ( blocks, parentLayout ) => {
67
84
  }, [ blocks, parentLayout ] );
68
85
  };
69
86
 
87
+ function applyInitialOverrides( blocks, overrides = {}, defaultValues ) {
88
+ return blocks.map( ( block ) => {
89
+ const innerBlocks = applyInitialOverrides(
90
+ block.innerBlocks,
91
+ overrides,
92
+ defaultValues
93
+ );
94
+ const blockId = block.attributes.metadata?.id;
95
+ if ( ! isPartiallySynced( block ) || ! blockId )
96
+ return { ...block, innerBlocks };
97
+ const attributes = getPartiallySyncedAttributes( block );
98
+ const newAttributes = { ...block.attributes };
99
+ for ( const attributeKey of attributes ) {
100
+ defaultValues[ blockId ] = block.attributes[ attributeKey ];
101
+ if ( overrides[ blockId ] ) {
102
+ newAttributes[ attributeKey ] = overrides[ blockId ];
103
+ }
104
+ }
105
+ return {
106
+ ...block,
107
+ attributes: newAttributes,
108
+ innerBlocks,
109
+ };
110
+ } );
111
+ }
112
+
113
+ function getOverridesFromBlocks( blocks, defaultValues ) {
114
+ /** @type {Record<string, unknown>} */
115
+ const overrides = {};
116
+ for ( const block of blocks ) {
117
+ Object.assign(
118
+ overrides,
119
+ getOverridesFromBlocks( block.innerBlocks, defaultValues )
120
+ );
121
+ const blockId = block.attributes.metadata?.id;
122
+ if ( ! isPartiallySynced( block ) || ! blockId ) continue;
123
+ const attributes = getPartiallySyncedAttributes( block );
124
+ for ( const attributeKey of attributes ) {
125
+ if (
126
+ block.attributes[ attributeKey ] !== defaultValues[ blockId ]
127
+ ) {
128
+ overrides[ blockId ] = block.attributes[ attributeKey ];
129
+ }
130
+ }
131
+ }
132
+ return Object.keys( overrides ).length > 0 ? overrides : undefined;
133
+ }
134
+
70
135
  export default function ReusableBlockEdit( {
71
136
  name,
72
- attributes: { ref },
137
+ attributes: { ref, overrides },
73
138
  __unstableParentLayout: parentLayout,
139
+ clientId: patternClientId,
74
140
  } ) {
141
+ const registry = useRegistry();
75
142
  const hasAlreadyRendered = useHasRecursion( ref );
76
143
  const { record, hasResolved } = useEntityRecord(
77
144
  'postType',
@@ -79,11 +146,46 @@ export default function ReusableBlockEdit( {
79
146
  ref
80
147
  );
81
148
  const isMissing = hasResolved && ! record;
149
+ const initialOverrides = useRef( overrides );
150
+ const defaultValuesRef = useRef( {} );
151
+ const {
152
+ replaceInnerBlocks,
153
+ __unstableMarkNextChangeAsNotPersistent,
154
+ setBlockEditingMode,
155
+ } = useDispatch( blockEditorStore );
156
+ const { getBlockEditingMode } = useSelect( blockEditorStore );
82
157
 
83
- const [ blocks, onInput, onChange ] = useEntityBlockEditor(
84
- 'postType',
85
- 'wp_block',
86
- { id: ref }
158
+ useEffect( () => {
159
+ if ( ! record?.content?.raw ) return;
160
+ const initialBlocks = parse( record.content.raw );
161
+
162
+ const editingMode = getBlockEditingMode( patternClientId );
163
+ registry.batch( () => {
164
+ setBlockEditingMode( patternClientId, 'default' );
165
+ __unstableMarkNextChangeAsNotPersistent();
166
+ replaceInnerBlocks(
167
+ patternClientId,
168
+ applyInitialOverrides(
169
+ initialBlocks,
170
+ initialOverrides.current,
171
+ defaultValuesRef.current
172
+ )
173
+ );
174
+ setBlockEditingMode( patternClientId, editingMode );
175
+ } );
176
+ }, [
177
+ __unstableMarkNextChangeAsNotPersistent,
178
+ patternClientId,
179
+ record,
180
+ replaceInnerBlocks,
181
+ registry,
182
+ getBlockEditingMode,
183
+ setBlockEditingMode,
184
+ ] );
185
+
186
+ const innerBlocks = useSelect(
187
+ ( select ) => select( blockEditorStore ).getBlocks( patternClientId ),
188
+ [ patternClientId ]
87
189
  );
88
190
 
89
191
  const [ title, setTitle ] = useEntityProp(
@@ -93,7 +195,10 @@ export default function ReusableBlockEdit( {
93
195
  ref
94
196
  );
95
197
 
96
- const { alignment, layout } = useInferredLayout( blocks, parentLayout );
198
+ const { alignment, layout } = useInferredLayout(
199
+ innerBlocks,
200
+ parentLayout
201
+ );
97
202
  const layoutClasses = useLayoutClasses( { layout }, name );
98
203
 
99
204
  const blockProps = useBlockProps( {
@@ -105,16 +210,38 @@ export default function ReusableBlockEdit( {
105
210
  } );
106
211
 
107
212
  const innerBlocksProps = useInnerBlocksProps( blockProps, {
108
- value: blocks,
109
213
  layout,
110
- onInput,
111
- onChange,
112
- renderAppender: blocks?.length
214
+ renderAppender: innerBlocks?.length
113
215
  ? undefined
114
216
  : InnerBlocks.ButtonBlockAppender,
115
217
  } );
116
218
 
219
+ // Sync the `overrides` attribute from the updated blocks.
220
+ // `syncDerivedBlockAttributes` is an action that just like `updateBlockAttributes`
221
+ // but won't create an undo level.
222
+ // This can be abstracted into a `useSyncDerivedAttributes` hook if needed.
223
+ useEffect( () => {
224
+ const { getBlocks } = registry.select( blockEditorStore );
225
+ const { syncDerivedBlockAttributes } = unlock(
226
+ registry.dispatch( blockEditorStore )
227
+ );
228
+ let prevBlocks = getBlocks( patternClientId );
229
+ return registry.subscribe( () => {
230
+ const blocks = getBlocks( patternClientId );
231
+ if ( blocks !== prevBlocks ) {
232
+ prevBlocks = blocks;
233
+ syncDerivedBlockAttributes( patternClientId, {
234
+ overrides: getOverridesFromBlocks(
235
+ blocks,
236
+ defaultValuesRef.current
237
+ ),
238
+ } );
239
+ }
240
+ }, blockEditorStore );
241
+ }, [ patternClientId, registry ] );
242
+
117
243
  let children = null;
244
+
118
245
  if ( hasAlreadyRendered ) {
119
246
  children = (
120
247
  <Warning>
@@ -8,14 +8,15 @@ import { symbol as icon } from '@wordpress/icons';
8
8
  */
9
9
  import initBlock from '../utils/init-block';
10
10
  import metadata from './block.json';
11
- import edit from './edit';
11
+ import editV1 from './v1/edit';
12
+ import editV2 from './edit';
12
13
 
13
14
  const { name } = metadata;
14
15
 
15
16
  export { metadata, name };
16
17
 
17
18
  export const settings = {
18
- edit,
19
+ edit: window.__experimentalPatternPartialSyncing ? editV2 : editV1,
19
20
  icon,
20
21
  };
21
22
 
@@ -46,8 +46,31 @@ function render_block_core_block( $attributes ) {
46
46
  $content = $wp_embed->run_shortcode( $reusable_block->post_content );
47
47
  $content = $wp_embed->autoembed( $content );
48
48
 
49
+ $gutenberg_experiments = get_option( 'gutenberg-experiments' );
50
+ $has_partial_synced_overrides = $gutenberg_experiments
51
+ && array_key_exists( 'gutenberg-pattern-partial-syncing', $gutenberg_experiments )
52
+ && isset( $attributes['overrides'] );
53
+
54
+ /**
55
+ * We set the `pattern/overrides` context through the `render_block_context`
56
+ * filter so that it is available when a pattern's inner blocks are
57
+ * rendering via do_blocks given it only receives the inner content.
58
+ */
59
+ if ( $has_partial_synced_overrides ) {
60
+ $filter_block_context = static function ( $context ) use ( $attributes ) {
61
+ $context['pattern/overrides'] = $attributes['overrides'];
62
+ return $context;
63
+ };
64
+ add_filter( 'render_block_context', $filter_block_context, 1 );
65
+ }
66
+
49
67
  $content = do_blocks( $content );
50
68
  unset( $seen_refs[ $attributes['ref'] ] );
69
+
70
+ if ( $has_partial_synced_overrides ) {
71
+ remove_filter( 'render_block_context', $filter_block_context, 1 );
72
+ }
73
+
51
74
  return $content;
52
75
  }
53
76
 
@@ -63,3 +86,28 @@ function register_block_core_block() {
63
86
  );
64
87
  }
65
88
  add_action( 'init', 'register_block_core_block' );
89
+
90
+ $gutenberg_experiments = get_option( 'gutenberg-experiments' );
91
+ if ( $gutenberg_experiments && array_key_exists( 'gutenberg-pattern-partial-syncing', $gutenberg_experiments ) ) {
92
+ /**
93
+ * Registers the overrides attribute for core/block.
94
+ *
95
+ * @param array $args Array of arguments for registering a block type.
96
+ * @param string $block_name Block name including namespace.
97
+ * @return array $args
98
+ */
99
+ function register_block_core_block_args( $args, $block_name ) {
100
+ if ( 'core/block' === $block_name ) {
101
+ $args['attributes'] = array_merge(
102
+ $args['attributes'],
103
+ array(
104
+ 'overrides' => array(
105
+ 'type' => 'object',
106
+ ),
107
+ )
108
+ );
109
+ }
110
+ return $args;
111
+ }
112
+ add_filter( 'register_block_type_args', 'register_block_core_block_args', 10, 2 );
113
+ }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import classnames from 'classnames';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import {
10
+ useEntityBlockEditor,
11
+ useEntityProp,
12
+ useEntityRecord,
13
+ } from '@wordpress/core-data';
14
+ import {
15
+ Placeholder,
16
+ Spinner,
17
+ TextControl,
18
+ PanelBody,
19
+ } from '@wordpress/components';
20
+ import { __ } from '@wordpress/i18n';
21
+ import {
22
+ useInnerBlocksProps,
23
+ __experimentalRecursionProvider as RecursionProvider,
24
+ __experimentalUseHasRecursion as useHasRecursion,
25
+ InnerBlocks,
26
+ InspectorControls,
27
+ useBlockProps,
28
+ Warning,
29
+ privateApis as blockEditorPrivateApis,
30
+ } from '@wordpress/block-editor';
31
+ import { useRef, useMemo } from '@wordpress/element';
32
+
33
+ /**
34
+ * Internal dependencies
35
+ */
36
+ import { unlock } from '../../lock-unlock';
37
+
38
+ const { useLayoutClasses } = unlock( blockEditorPrivateApis );
39
+ const fullAlignments = [ 'full', 'wide', 'left', 'right' ];
40
+
41
+ const useInferredLayout = ( blocks, parentLayout ) => {
42
+ const initialInferredAlignmentRef = useRef();
43
+
44
+ return useMemo( () => {
45
+ // Exit early if the pattern's blocks haven't loaded yet.
46
+ if ( ! blocks?.length ) {
47
+ return {};
48
+ }
49
+
50
+ let alignment = initialInferredAlignmentRef.current;
51
+
52
+ // Only track the initial alignment so that temporarily removed
53
+ // alignments can be reapplied.
54
+ if ( alignment === undefined ) {
55
+ const isConstrained = parentLayout?.type === 'constrained';
56
+ const hasFullAlignment = blocks.some( ( block ) =>
57
+ fullAlignments.includes( block.attributes.align )
58
+ );
59
+
60
+ alignment = isConstrained && hasFullAlignment ? 'full' : null;
61
+ initialInferredAlignmentRef.current = alignment;
62
+ }
63
+
64
+ const layout = alignment ? parentLayout : undefined;
65
+
66
+ return { alignment, layout };
67
+ }, [ blocks, parentLayout ] );
68
+ };
69
+
70
+ export default function ReusableBlockEdit( {
71
+ name,
72
+ attributes: { ref },
73
+ __unstableParentLayout: parentLayout,
74
+ } ) {
75
+ const hasAlreadyRendered = useHasRecursion( ref );
76
+ const { record, hasResolved } = useEntityRecord(
77
+ 'postType',
78
+ 'wp_block',
79
+ ref
80
+ );
81
+ const isMissing = hasResolved && ! record;
82
+
83
+ const [ blocks, onInput, onChange ] = useEntityBlockEditor(
84
+ 'postType',
85
+ 'wp_block',
86
+ { id: ref }
87
+ );
88
+
89
+ const [ title, setTitle ] = useEntityProp(
90
+ 'postType',
91
+ 'wp_block',
92
+ 'title',
93
+ ref
94
+ );
95
+
96
+ const { alignment, layout } = useInferredLayout( blocks, parentLayout );
97
+ const layoutClasses = useLayoutClasses( { layout }, name );
98
+
99
+ const blockProps = useBlockProps( {
100
+ className: classnames(
101
+ 'block-library-block__reusable-block-container',
102
+ layout && layoutClasses,
103
+ { [ `align${ alignment }` ]: alignment }
104
+ ),
105
+ } );
106
+
107
+ const innerBlocksProps = useInnerBlocksProps( blockProps, {
108
+ value: blocks,
109
+ layout,
110
+ onInput,
111
+ onChange,
112
+ renderAppender: blocks?.length
113
+ ? undefined
114
+ : InnerBlocks.ButtonBlockAppender,
115
+ } );
116
+
117
+ let children = null;
118
+
119
+ if ( hasAlreadyRendered ) {
120
+ children = (
121
+ <Warning>
122
+ { __( 'Block cannot be rendered inside itself.' ) }
123
+ </Warning>
124
+ );
125
+ }
126
+
127
+ if ( isMissing ) {
128
+ children = (
129
+ <Warning>
130
+ { __( 'Block has been deleted or is unavailable.' ) }
131
+ </Warning>
132
+ );
133
+ }
134
+
135
+ if ( ! hasResolved ) {
136
+ children = (
137
+ <Placeholder>
138
+ <Spinner />
139
+ </Placeholder>
140
+ );
141
+ }
142
+
143
+ return (
144
+ <RecursionProvider uniqueId={ ref }>
145
+ <InspectorControls>
146
+ <PanelBody>
147
+ <TextControl
148
+ label={ __( 'Name' ) }
149
+ value={ title }
150
+ onChange={ setTitle }
151
+ __nextHasNoMarginBottom
152
+ __next40pxDefaultSize
153
+ />
154
+ </PanelBody>
155
+ </InspectorControls>
156
+ { children === null ? (
157
+ <div { ...innerBlocksProps } />
158
+ ) : (
159
+ <div { ...blockProps }>{ children }</div>
160
+ ) }
161
+ </RecursionProvider>
162
+ );
163
+ }
@@ -42,8 +42,8 @@ import { store as noticesStore } from '@wordpress/notices';
42
42
  /**
43
43
  * Internal dependencies
44
44
  */
45
- import styles from './editor.scss';
46
- import EditTitle from './edit-title';
45
+ import styles from '../editor.scss';
46
+ import EditTitle from '../edit-title';
47
47
 
48
48
  export default function ReusableBlockEdit( {
49
49
  attributes: { ref },
@@ -36,8 +36,8 @@
36
36
  "__experimentalRole": "content"
37
37
  },
38
38
  "text": {
39
- "type": "string",
40
- "source": "html",
39
+ "type": "rich-text",
40
+ "source": "rich-text",
41
41
  "selector": "a,button",
42
42
  "__experimentalRole": "content"
43
43
  },
@@ -30,7 +30,7 @@ export default function save( { attributes, className } ) {
30
30
  width,
31
31
  } = attributes;
32
32
 
33
- if ( ! text ) {
33
+ if ( RichText.isEmpty( text ) ) {
34
34
  return null;
35
35
  }
36
36
 
@@ -8,8 +8,8 @@
8
8
  "textdomain": "default",
9
9
  "attributes": {
10
10
  "content": {
11
- "type": "string",
12
- "source": "html",
11
+ "type": "rich-text",
12
+ "source": "rich-text",
13
13
  "selector": "code",
14
14
  "__unstablePreserveWhiteSpace": true
15
15
  }