@wordpress/block-library 9.34.1-next.2f1c7c01b.0 → 9.35.1-next.16d95556a.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 (172) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/block/edit.js +2 -2
  3. package/build/block/edit.js.map +2 -2
  4. package/build/block-keyboard-shortcuts/index.js +17 -7
  5. package/build/block-keyboard-shortcuts/index.js.map +2 -2
  6. package/build/cover/deprecated.js +15 -3
  7. package/build/cover/deprecated.js.map +2 -2
  8. package/build/cover/edit/inspector-controls.js +1 -1
  9. package/build/cover/edit/inspector-controls.js.map +2 -2
  10. package/build/cover/transforms.js +10 -2
  11. package/build/cover/transforms.js.map +2 -2
  12. package/build/embed/icons.js +2 -2
  13. package/build/embed/icons.js.map +2 -2
  14. package/build/embed/variations.js +3 -3
  15. package/build/embed/variations.js.map +2 -2
  16. package/build/heading/index.js +3 -1
  17. package/build/heading/index.js.map +3 -3
  18. package/build/heading/transforms.js +10 -3
  19. package/build/heading/transforms.js.map +2 -2
  20. package/build/heading/variations.js +55 -0
  21. package/build/heading/variations.js.map +7 -0
  22. package/build/html/edit.js +54 -44
  23. package/build/html/edit.js.map +3 -3
  24. package/build/html/modal.js +328 -0
  25. package/build/html/modal.js.map +7 -0
  26. package/build/html/utils.js +72 -0
  27. package/build/html/utils.js.map +7 -0
  28. package/build/navigation-link/edit.js +25 -10
  29. package/build/navigation-link/edit.js.map +2 -2
  30. package/build/navigation-link/link-ui/index.js +8 -3
  31. package/build/navigation-link/link-ui/index.js.map +2 -2
  32. package/build/navigation-link/shared/controls.js +42 -7
  33. package/build/navigation-link/shared/controls.js.map +2 -2
  34. package/build/navigation-link/shared/use-entity-binding.js +31 -2
  35. package/build/navigation-link/shared/use-entity-binding.js.map +3 -3
  36. package/build/paragraph/block.json +1 -3
  37. package/build/paragraph/deprecated.js +65 -12
  38. package/build/paragraph/deprecated.js.map +2 -2
  39. package/build/paragraph/edit.js +14 -25
  40. package/build/paragraph/edit.js.map +2 -2
  41. package/build/paragraph/index.js +3 -1
  42. package/build/paragraph/index.js.map +3 -3
  43. package/build/paragraph/save.js +3 -3
  44. package/build/paragraph/save.js.map +2 -2
  45. package/build/paragraph/transforms.js +7 -1
  46. package/build/paragraph/transforms.js.map +2 -2
  47. package/build/paragraph/variations.js +57 -0
  48. package/build/paragraph/variations.js.map +7 -0
  49. package/build/pullquote/block.json +3 -2
  50. package/build/pullquote/transforms.js +0 -31
  51. package/build/pullquote/transforms.js.map +2 -2
  52. package/build/quote/transforms.js +0 -20
  53. package/build/quote/transforms.js.map +2 -2
  54. package/build-module/block/edit.js +2 -2
  55. package/build-module/block/edit.js.map +2 -2
  56. package/build-module/block-keyboard-shortcuts/index.js +17 -7
  57. package/build-module/block-keyboard-shortcuts/index.js.map +2 -2
  58. package/build-module/cover/deprecated.js +15 -3
  59. package/build-module/cover/deprecated.js.map +2 -2
  60. package/build-module/cover/edit/inspector-controls.js +1 -1
  61. package/build-module/cover/edit/inspector-controls.js.map +2 -2
  62. package/build-module/cover/transforms.js +10 -2
  63. package/build-module/cover/transforms.js.map +2 -2
  64. package/build-module/embed/icons.js +2 -2
  65. package/build-module/embed/icons.js.map +2 -2
  66. package/build-module/embed/variations.js +3 -3
  67. package/build-module/embed/variations.js.map +2 -2
  68. package/build-module/heading/index.js +3 -1
  69. package/build-module/heading/index.js.map +2 -2
  70. package/build-module/heading/transforms.js +10 -3
  71. package/build-module/heading/transforms.js.map +2 -2
  72. package/build-module/heading/variations.js +34 -0
  73. package/build-module/heading/variations.js.map +7 -0
  74. package/build-module/html/edit.js +62 -51
  75. package/build-module/html/edit.js.map +2 -2
  76. package/build-module/html/modal.js +304 -0
  77. package/build-module/html/modal.js.map +7 -0
  78. package/build-module/html/utils.js +46 -0
  79. package/build-module/html/utils.js.map +7 -0
  80. package/build-module/navigation-link/edit.js +25 -10
  81. package/build-module/navigation-link/edit.js.map +2 -2
  82. package/build-module/navigation-link/link-ui/index.js +8 -3
  83. package/build-module/navigation-link/link-ui/index.js.map +2 -2
  84. package/build-module/navigation-link/shared/controls.js +42 -7
  85. package/build-module/navigation-link/shared/controls.js.map +2 -2
  86. package/build-module/navigation-link/shared/use-entity-binding.js +35 -3
  87. package/build-module/navigation-link/shared/use-entity-binding.js.map +2 -2
  88. package/build-module/paragraph/block.json +1 -3
  89. package/build-module/paragraph/deprecated.js +65 -12
  90. package/build-module/paragraph/deprecated.js.map +2 -2
  91. package/build-module/paragraph/edit.js +14 -26
  92. package/build-module/paragraph/edit.js.map +2 -2
  93. package/build-module/paragraph/index.js +3 -1
  94. package/build-module/paragraph/index.js.map +2 -2
  95. package/build-module/paragraph/save.js +3 -3
  96. package/build-module/paragraph/save.js.map +2 -2
  97. package/build-module/paragraph/transforms.js +7 -1
  98. package/build-module/paragraph/transforms.js.map +2 -2
  99. package/build-module/paragraph/variations.js +36 -0
  100. package/build-module/paragraph/variations.js.map +7 -0
  101. package/build-module/pullquote/block.json +3 -2
  102. package/build-module/pullquote/transforms.js +0 -31
  103. package/build-module/pullquote/transforms.js.map +2 -2
  104. package/build-module/quote/transforms.js +0 -20
  105. package/build-module/quote/transforms.js.map +2 -2
  106. package/build-style/accordion-heading/style-rtl.css +19 -3
  107. package/build-style/accordion-heading/style.css +19 -3
  108. package/build-style/accordion-panel/style-rtl.css +4 -1
  109. package/build-style/accordion-panel/style.css +4 -1
  110. package/build-style/common-rtl.css +3 -3
  111. package/build-style/common.css +3 -3
  112. package/build-style/editor-rtl.css +62 -21
  113. package/build-style/editor.css +62 -21
  114. package/build-style/embed/style-rtl.css +5 -0
  115. package/build-style/embed/style.css +5 -0
  116. package/build-style/html/editor-rtl.css +55 -21
  117. package/build-style/html/editor.css +55 -21
  118. package/build-style/navigation-link/editor-rtl.css +7 -0
  119. package/build-style/navigation-link/editor.css +7 -0
  120. package/build-style/style-rtl.css +31 -7
  121. package/build-style/style.css +31 -7
  122. package/package.json +37 -37
  123. package/src/accordion-heading/style.scss +40 -7
  124. package/src/accordion-panel/style.scss +6 -1
  125. package/src/block/edit.js +2 -2
  126. package/src/block-keyboard-shortcuts/index.js +23 -9
  127. package/src/common.scss +6 -5
  128. package/src/cover/deprecated.js +15 -3
  129. package/src/cover/edit/inspector-controls.js +1 -1
  130. package/src/cover/transforms.js +10 -2
  131. package/src/embed/icons.js +2 -4
  132. package/src/embed/style.scss +6 -0
  133. package/src/embed/variations.js +3 -3
  134. package/src/heading/index.js +2 -0
  135. package/src/heading/test/__snapshots__/transforms.native.js.snap +0 -6
  136. package/src/heading/test/transforms.native.js +1 -5
  137. package/src/heading/transforms.js +10 -3
  138. package/src/heading/variations.js +37 -0
  139. package/src/html/edit.js +62 -56
  140. package/src/html/editor.scss +69 -10
  141. package/src/html/modal.js +290 -0
  142. package/src/html/test/utils.js +234 -0
  143. package/src/html/utils.js +75 -0
  144. package/src/navigation-link/edit.js +44 -13
  145. package/src/navigation-link/editor.scss +7 -0
  146. package/src/navigation-link/index.php +65 -2
  147. package/src/navigation-link/link-ui/index.js +9 -8
  148. package/src/navigation-link/shared/controls.js +70 -12
  149. package/src/navigation-link/shared/test/controls.js +5 -0
  150. package/src/navigation-link/shared/test/use-entity-binding.js +14 -1
  151. package/src/navigation-link/shared/use-entity-binding.js +57 -9
  152. package/src/paragraph/block.json +1 -3
  153. package/src/paragraph/deprecated.js +87 -20
  154. package/src/paragraph/edit.js +7 -18
  155. package/src/paragraph/edit.native.js +18 -6
  156. package/src/paragraph/index.js +2 -0
  157. package/src/paragraph/save.js +4 -3
  158. package/src/paragraph/test/__snapshots__/transforms.native.js.snap +0 -6
  159. package/src/paragraph/test/edit.native.js +5 -5
  160. package/src/paragraph/test/transforms.native.js +0 -1
  161. package/src/paragraph/transforms.js +7 -1
  162. package/src/paragraph/variations.js +39 -0
  163. package/src/pullquote/block.json +3 -2
  164. package/src/pullquote/test/__snapshots__/transforms.native.js.snap +5 -5
  165. package/src/pullquote/test/transforms.native.js +1 -1
  166. package/src/pullquote/transforms.js +0 -31
  167. package/src/quote/test/__snapshots__/transforms.native.js.snap +0 -6
  168. package/src/quote/test/transforms.native.js +1 -5
  169. package/src/quote/transforms.js +0 -25
  170. package/src/utils/transformation-categories.native.js +0 -1
  171. package/tsconfig.tsbuildinfo +1 -1
  172. package/src/pullquote/test/edit.native.js +0 -73
@@ -87,10 +87,11 @@ export function Controls( { attributes, setAttributes, clientId } ) {
87
87
  }, [ url ] );
88
88
 
89
89
  // Use the entity binding hook internally
90
- const { hasUrlBinding, clearBinding } = useEntityBinding( {
91
- clientId,
92
- attributes,
93
- } );
90
+ const { hasUrlBinding, isBoundEntityAvailable, clearBinding } =
91
+ useEntityBinding( {
92
+ clientId,
93
+ attributes,
94
+ } );
94
95
 
95
96
  // Get direct store dispatch to bypass setBoundAttributes wrapper
96
97
  const { updateBlockAttributes } = useDispatch( blockEditorStore );
@@ -111,8 +112,7 @@ export function Controls( { attributes, setAttributes, clientId } ) {
111
112
  };
112
113
 
113
114
  useEffect( () => {
114
- // Checking for ! hasUrlBinding is a defensive check, as we would
115
- // only want to focus the input if the url is not bound to an entity.
115
+ // Only want to focus the input if the url is not bound to an entity.
116
116
  if ( ! hasUrlBinding && shouldFocusURLInputRef.current ) {
117
117
  // focuses and highlights the url input value, giving the user
118
118
  // the ability to delete the value quickly or edit it.
@@ -165,12 +165,28 @@ export function Controls( { attributes, setAttributes, clientId } ) {
165
165
  __next40pxDefaultSize
166
166
  id={ inputId }
167
167
  label={ __( 'Link' ) }
168
- value={ inputValue ? safeDecodeURI( inputValue ) : '' }
168
+ value={ ( () => {
169
+ if ( hasUrlBinding && ! isBoundEntityAvailable ) {
170
+ return '';
171
+ }
172
+ return inputValue ? safeDecodeURI( inputValue ) : '';
173
+ } )() }
169
174
  autoComplete="off"
170
175
  type="url"
171
176
  disabled={ hasUrlBinding }
177
+ aria-invalid={
178
+ hasUrlBinding && ! isBoundEntityAvailable
179
+ ? 'true'
180
+ : undefined
181
+ }
182
+ aria-describedby={ helpTextId }
183
+ className={
184
+ hasUrlBinding && ! isBoundEntityAvailable
185
+ ? 'navigation-link-control__input-with-error-suffix'
186
+ : undefined
187
+ }
172
188
  onChange={ ( newValue ) => {
173
- if ( hasUrlBinding ) {
189
+ if ( isBoundEntityAvailable ) {
174
190
  return;
175
191
  }
176
192
 
@@ -180,13 +196,13 @@ export function Controls( { attributes, setAttributes, clientId } ) {
180
196
  setInputValue( newValue );
181
197
  } }
182
198
  onFocus={ () => {
183
- if ( hasUrlBinding ) {
199
+ if ( isBoundEntityAvailable ) {
184
200
  return;
185
201
  }
186
202
  lastURLRef.current = url;
187
203
  } }
188
204
  onBlur={ () => {
189
- if ( hasUrlBinding ) {
205
+ if ( isBoundEntityAvailable ) {
190
206
  return;
191
207
  }
192
208
 
@@ -204,11 +220,19 @@ export function Controls( { attributes, setAttributes, clientId } ) {
204
220
  } );
205
221
  } }
206
222
  help={
207
- hasUrlBinding && (
208
- <BindingHelpText
223
+ hasUrlBinding && ! isBoundEntityAvailable ? (
224
+ <MissingEntityHelpText
225
+ id={ helpTextId }
209
226
  type={ attributes.type }
210
227
  kind={ attributes.kind }
211
228
  />
229
+ ) : (
230
+ isBoundEntityAvailable && (
231
+ <BindingHelpText
232
+ type={ attributes.type }
233
+ kind={ attributes.kind }
234
+ />
235
+ )
212
236
  )
213
237
  }
214
238
  suffix={
@@ -225,6 +249,11 @@ export function Controls( { attributes, setAttributes, clientId } ) {
225
249
  showTooltip
226
250
  label={ __( 'Unsync and edit' ) }
227
251
  __next40pxDefaultSize
252
+ className={
253
+ hasUrlBinding && ! isBoundEntityAvailable
254
+ ? 'navigation-link-control__error-suffix-button'
255
+ : undefined
256
+ }
228
257
  />
229
258
  )
230
259
  }
@@ -306,3 +335,32 @@ function BindingHelpText( { type, kind } ) {
306
335
  entityType
307
336
  );
308
337
  }
338
+
339
+ /**
340
+ * Component to display error help text for missing entity bindings.
341
+ *
342
+ * @param {Object} props - Component props
343
+ * @param {string} props.id - ID for the help text element (for aria-describedby)
344
+ * @param {string} props.type - The entity type
345
+ * @param {string} props.kind - The entity kind
346
+ * @return {JSX.Element} Error help text component
347
+ */
348
+ function MissingEntityHelpText( { id, type, kind } ) {
349
+ const entityType = getEntityTypeName( type, kind );
350
+ return (
351
+ <span
352
+ id={ id }
353
+ className="navigation-link-control__error-text"
354
+ role="alert"
355
+ aria-live="polite"
356
+ >
357
+ { sprintf(
358
+ /* translators: %s is the entity type (e.g., "page", "post", "category") */
359
+ __(
360
+ 'Synced %s is missing. Please update or remove this link.'
361
+ ),
362
+ entityType
363
+ ) }
364
+ </span>
365
+ );
366
+ }
@@ -28,6 +28,7 @@ jest.mock( '../../../utils/hooks', () => ( {
28
28
  jest.mock( '../use-entity-binding', () => ( {
29
29
  useEntityBinding: jest.fn( () => ( {
30
30
  hasUrlBinding: false,
31
+ isBoundEntityAvailable: false,
31
32
  clearBinding: jest.fn(),
32
33
  } ) ),
33
34
  } ) );
@@ -202,6 +203,7 @@ describe( 'Controls', () => {
202
203
  const { useEntityBinding } = require( '../use-entity-binding' );
203
204
  useEntityBinding.mockReturnValue( {
204
205
  hasUrlBinding: true,
206
+ isBoundEntityAvailable: true,
205
207
  clearBinding: jest.fn(),
206
208
  } );
207
209
 
@@ -225,6 +227,7 @@ describe( 'Controls', () => {
225
227
  const { useEntityBinding } = require( '../use-entity-binding' );
226
228
  useEntityBinding.mockReturnValue( {
227
229
  hasUrlBinding: true,
230
+ isBoundEntityAvailable: true,
228
231
  clearBinding: jest.fn(),
229
232
  } );
230
233
 
@@ -262,6 +265,7 @@ describe( 'Controls', () => {
262
265
  const { useEntityBinding } = require( '../use-entity-binding' );
263
266
  useEntityBinding.mockReturnValue( {
264
267
  hasUrlBinding: true,
268
+ isBoundEntityAvailable: true,
265
269
  clearBinding: jest.fn(),
266
270
  } );
267
271
 
@@ -285,6 +289,7 @@ describe( 'Controls', () => {
285
289
  const { useEntityBinding } = require( '../use-entity-binding' );
286
290
  useEntityBinding.mockReturnValue( {
287
291
  hasUrlBinding: true,
292
+ isBoundEntityAvailable: true,
288
293
  clearBinding: jest.fn(),
289
294
  } );
290
295
 
@@ -18,12 +18,23 @@ import {
18
18
  // Mock the entire @wordpress/block-editor module
19
19
  jest.mock( '@wordpress/block-editor', () => ( {
20
20
  useBlockBindingsUtils: jest.fn(),
21
+ useBlockEditingMode: jest.fn(),
21
22
  } ) );
22
23
 
24
+ // Mock useSelect specifically to avoid needing to set up full data store
25
+ jest.mock( '@wordpress/data/src/components/use-select', () => {
26
+ const mock = jest.fn();
27
+ return mock;
28
+ } );
29
+
23
30
  /**
24
31
  * WordPress dependencies
25
32
  */
26
- import { useBlockBindingsUtils } from '@wordpress/block-editor';
33
+ import {
34
+ useBlockBindingsUtils,
35
+ useBlockEditingMode,
36
+ } from '@wordpress/block-editor';
37
+ import { useSelect } from '@wordpress/data';
27
38
 
28
39
  describe( 'useEntityBinding', () => {
29
40
  const mockUpdateBlockBindings = jest.fn();
@@ -33,6 +44,8 @@ describe( 'useEntityBinding', () => {
33
44
  useBlockBindingsUtils.mockReturnValue( {
34
45
  updateBlockBindings: mockUpdateBlockBindings,
35
46
  } );
47
+ useBlockEditingMode.mockReturnValue( 'default' );
48
+ useSelect.mockReturnValue( true );
36
49
  } );
37
50
 
38
51
  describe( 'hasUrlBinding', () => {
@@ -2,7 +2,12 @@
2
2
  * WordPress dependencies
3
3
  */
4
4
  import { useCallback } from '@wordpress/element';
5
- import { useBlockBindingsUtils } from '@wordpress/block-editor';
5
+ import {
6
+ useBlockBindingsUtils,
7
+ useBlockEditingMode,
8
+ } from '@wordpress/block-editor';
9
+ import { useSelect } from '@wordpress/data';
10
+ import { store as coreStore } from '@wordpress/core-data';
6
11
 
7
12
  /**
8
13
  * Builds entity binding configuration for navigation link URLs.
@@ -16,7 +21,7 @@ import { useBlockBindingsUtils } from '@wordpress/block-editor';
16
21
  * @throws {Error} If kind is not 'post-type' or 'taxonomy'
17
22
  */
18
23
  export function buildNavigationLinkEntityBinding( kind ) {
19
- // Validate kind parameter exists
24
+ // Validate kind parameter exists.
20
25
  if ( kind === undefined ) {
21
26
  throw new Error(
22
27
  'buildNavigationLinkEntityBinding requires a kind parameter. ' +
@@ -24,7 +29,7 @@ export function buildNavigationLinkEntityBinding( kind ) {
24
29
  );
25
30
  }
26
31
 
27
- // Validate kind parameter value
32
+ // Validate kind parameter value.
28
33
  if ( kind !== 'post-type' && kind !== 'taxonomy' ) {
29
34
  throw new Error(
30
35
  `Invalid kind "${ kind }" provided to buildNavigationLinkEntityBinding. ` +
@@ -57,7 +62,8 @@ export function buildNavigationLinkEntityBinding( kind ) {
57
62
  */
58
63
  export function useEntityBinding( { clientId, attributes } ) {
59
64
  const { updateBlockBindings } = useBlockBindingsUtils( clientId );
60
- const { metadata, id, kind } = attributes;
65
+ const { metadata, id, kind, type } = attributes;
66
+ const blockEditingMode = useBlockEditingMode();
61
67
 
62
68
  const hasUrlBinding = !! metadata?.bindings?.url && !! id;
63
69
  const expectedSource =
@@ -65,6 +71,47 @@ export function useEntityBinding( { clientId, attributes } ) {
65
71
  const hasCorrectBinding =
66
72
  hasUrlBinding && metadata?.bindings?.url?.source === expectedSource;
67
73
 
74
+ // Check if the bound entity is available (not deleted).
75
+ const isBoundEntityAvailable = useSelect(
76
+ ( select ) => {
77
+ // First check: metadata/binding must exist
78
+ if ( ! hasCorrectBinding || ! id ) {
79
+ return false;
80
+ }
81
+
82
+ const isPostType = kind === 'post-type';
83
+ const isTaxonomy = kind === 'taxonomy';
84
+
85
+ // Only check entity availability for post types and taxonomies.
86
+ if ( ! isPostType && ! isTaxonomy ) {
87
+ return false;
88
+ }
89
+
90
+ // Skip check in disabled contexts to avoid unnecessary requests.
91
+ if ( blockEditingMode === 'disabled' ) {
92
+ return true; // Assume available in disabled contexts.
93
+ }
94
+
95
+ // Second check: entity must exist
96
+ const { getEntityRecord, hasFinishedResolution } =
97
+ select( coreStore );
98
+
99
+ // Use the correct entity type based on kind.
100
+ const entityType = isTaxonomy ? 'taxonomy' : 'postType';
101
+ const entityRecord = getEntityRecord( entityType, type, id );
102
+ const hasResolved = hasFinishedResolution( 'getEntityRecord', [
103
+ entityType,
104
+ type,
105
+ id,
106
+ ] );
107
+
108
+ // If resolution has finished and entityRecord is undefined, the entity was deleted.
109
+ // Return true if entity exists, false if deleted.
110
+ return hasResolved ? entityRecord !== undefined : true;
111
+ },
112
+ [ kind, type, id, hasCorrectBinding, blockEditingMode ]
113
+ );
114
+
68
115
  const clearBinding = useCallback( () => {
69
116
  if ( hasUrlBinding ) {
70
117
  updateBlockBindings( { url: undefined } );
@@ -73,11 +120,11 @@ export function useEntityBinding( { clientId, attributes } ) {
73
120
 
74
121
  const createBinding = useCallback(
75
122
  ( updatedAttributes ) => {
76
- // Use updated attributes if provided, otherwise fall back to closure attributes
77
- // updatedAttributes needed to access the most up-to-date data when called synchronously
123
+ // Use updated attributes if provided, otherwise fall back to closure attributes.
124
+ // updatedAttributes needed to access the most up-to-date data when called synchronously.
78
125
  const kindToUse = updatedAttributes?.kind ?? kind;
79
126
 
80
- // Avoid creating binding if no kind is provided
127
+ // Avoid creating binding if no kind is provided.
81
128
  if ( ! kindToUse ) {
82
129
  return;
83
130
  }
@@ -91,14 +138,15 @@ export function useEntityBinding( { clientId, attributes } ) {
91
138
  'Failed to create entity binding:',
92
139
  error.message
93
140
  );
94
- // Don't create binding if validation fails
141
+ // Don't create binding if validation fails.
95
142
  }
96
143
  },
97
- [ updateBlockBindings, kind, id ]
144
+ [ updateBlockBindings, kind ]
98
145
  );
99
146
 
100
147
  return {
101
148
  hasUrlBinding: hasCorrectBinding,
149
+ isBoundEntityAvailable,
102
150
  clearBinding,
103
151
  createBinding,
104
152
  };
@@ -8,9 +8,6 @@
8
8
  "keywords": [ "text" ],
9
9
  "textdomain": "default",
10
10
  "attributes": {
11
- "align": {
12
- "type": "string"
13
- },
14
11
  "content": {
15
12
  "type": "rich-text",
16
13
  "source": "rich-text",
@@ -58,6 +55,7 @@
58
55
  "typography": {
59
56
  "fontSize": true,
60
57
  "lineHeight": true,
58
+ "textAlign": true,
61
59
  "__experimentalFontFamily": true,
62
60
  "__experimentalTextDecoration": true,
63
61
  "__experimentalFontStyle": true,
@@ -90,9 +90,62 @@ const migrateCustomColorsAndFontSizes = ( attributes ) => {
90
90
  };
91
91
  };
92
92
 
93
+ const migrateTextAlign = ( attributes ) => {
94
+ const { align, ...restAttributes } = attributes;
95
+ if ( ! align ) {
96
+ return attributes;
97
+ }
98
+ return {
99
+ ...restAttributes,
100
+ style: {
101
+ ...attributes.style,
102
+ typography: {
103
+ ...attributes.style?.typography,
104
+ textAlign: align,
105
+ },
106
+ },
107
+ };
108
+ };
109
+
93
110
  const { style, ...restBlockAttributes } = blockAttributes;
94
111
 
95
112
  const deprecated = [
113
+ // Version with `align` attribute.
114
+ {
115
+ supports: {
116
+ className: false,
117
+ typography: {
118
+ fontSize: true,
119
+ },
120
+ },
121
+ attributes: blockAttributes,
122
+ isEligible( attributes ) {
123
+ return (
124
+ !! attributes.align ||
125
+ !! attributes.className?.match(
126
+ /\bhas-text-align-(left|center|right)\b/
127
+ )
128
+ );
129
+ },
130
+ save( { attributes } ) {
131
+ const { align, content, dropCap, direction } = attributes;
132
+ const className = clsx( {
133
+ 'has-drop-cap':
134
+ align === ( isRTL() ? 'left' : 'right' ) ||
135
+ align === 'center'
136
+ ? false
137
+ : dropCap,
138
+ [ `has-text-align-${ align }` ]: align,
139
+ } );
140
+
141
+ return (
142
+ <p { ...useBlockProps.save( { className, dir: direction } ) }>
143
+ <RichText.Content value={ content } />
144
+ </p>
145
+ );
146
+ },
147
+ migrate: migrateTextAlign,
148
+ },
96
149
  // Version without drop cap on aligned text.
97
150
  {
98
151
  supports,
@@ -108,6 +161,7 @@ const deprecated = [
108
161
  type: 'number',
109
162
  },
110
163
  },
164
+ migrate: migrateTextAlign,
111
165
  save( { attributes } ) {
112
166
  const { align, content, dropCap, direction } = attributes;
113
167
  const className = clsx( {
@@ -140,7 +194,11 @@ const deprecated = [
140
194
  type: 'number',
141
195
  },
142
196
  },
143
- migrate: migrateCustomColorsAndFontSizes,
197
+ migrate( attributes ) {
198
+ return migrateCustomColorsAndFontSizes(
199
+ migrateTextAlign( attributes )
200
+ );
201
+ },
144
202
  save( { attributes } ) {
145
203
  const {
146
204
  align,
@@ -205,7 +263,11 @@ const deprecated = [
205
263
  type: 'number',
206
264
  },
207
265
  },
208
- migrate: migrateCustomColorsAndFontSizes,
266
+ migrate( attributes ) {
267
+ return migrateCustomColorsAndFontSizes(
268
+ migrateTextAlign( attributes )
269
+ );
270
+ },
209
271
  save( { attributes } ) {
210
272
  const {
211
273
  align,
@@ -273,7 +335,11 @@ const deprecated = [
273
335
  type: 'string',
274
336
  },
275
337
  },
276
- migrate: migrateCustomColorsAndFontSizes,
338
+ migrate( attributes ) {
339
+ return migrateCustomColorsAndFontSizes(
340
+ migrateTextAlign( attributes )
341
+ );
342
+ },
277
343
  save( { attributes } ) {
278
344
  const {
279
345
  width,
@@ -363,21 +429,24 @@ const deprecated = [
363
429
  );
364
430
  },
365
431
  migrate( attributes ) {
366
- return migrateCustomColorsAndFontSizes( {
367
- ...attributes,
368
- customFontSize: Number.isFinite( attributes.fontSize )
369
- ? attributes.fontSize
370
- : undefined,
371
- customTextColor:
372
- attributes.textColor && '#' === attributes.textColor[ 0 ]
373
- ? attributes.textColor
374
- : undefined,
375
- customBackgroundColor:
376
- attributes.backgroundColor &&
377
- '#' === attributes.backgroundColor[ 0 ]
378
- ? attributes.backgroundColor
432
+ return migrateCustomColorsAndFontSizes(
433
+ migrateTextAlign( {
434
+ ...attributes,
435
+ customFontSize: Number.isFinite( attributes.fontSize )
436
+ ? attributes.fontSize
379
437
  : undefined,
380
- } );
438
+ customTextColor:
439
+ attributes.textColor &&
440
+ '#' === attributes.textColor[ 0 ]
441
+ ? attributes.textColor
442
+ : undefined,
443
+ customBackgroundColor:
444
+ attributes.backgroundColor &&
445
+ '#' === attributes.backgroundColor[ 0 ]
446
+ ? attributes.backgroundColor
447
+ : undefined,
448
+ } )
449
+ );
381
450
  },
382
451
  },
383
452
  {
@@ -393,9 +462,7 @@ const deprecated = [
393
462
  save( { attributes } ) {
394
463
  return <RawHTML>{ attributes.content }</RawHTML>;
395
464
  },
396
- migrate( attributes ) {
397
- return attributes;
398
- },
465
+ migrate: ( attributes ) => attributes,
399
466
  },
400
467
  ];
401
468
 
@@ -13,7 +13,6 @@ import {
13
13
  __experimentalToolsPanelItem as ToolsPanelItem,
14
14
  } from '@wordpress/components';
15
15
  import {
16
- AlignmentControl,
17
16
  BlockControls,
18
17
  InspectorControls,
19
18
  RichText,
@@ -58,10 +57,11 @@ function DropCapControl( { clientId, attributes, setAttributes, name } ) {
58
57
  return null;
59
58
  }
60
59
 
61
- const { align, dropCap } = attributes;
60
+ const { style, dropCap } = attributes;
61
+ const textAlign = style?.typography?.textAlign;
62
62
 
63
63
  let helpText;
64
- if ( hasDropCapDisabled( align ) ) {
64
+ if ( hasDropCapDisabled( textAlign ) ) {
65
65
  helpText = __( 'Not available for aligned text.' );
66
66
  } else if ( dropCap ) {
67
67
  helpText = __( 'Showing large initial letter.' );
@@ -91,7 +91,7 @@ function DropCapControl( { clientId, attributes, setAttributes, name } ) {
91
91
  checked={ !! dropCap }
92
92
  onChange={ () => setAttributes( { dropCap: ! dropCap } ) }
93
93
  help={ helpText }
94
- disabled={ hasDropCapDisabled( align ) }
94
+ disabled={ hasDropCapDisabled( textAlign ) }
95
95
  />
96
96
  </ToolsPanelItem>
97
97
  </InspectorControls>
@@ -108,12 +108,12 @@ function ParagraphBlock( {
108
108
  isSelected: isSingleSelected,
109
109
  name,
110
110
  } ) {
111
- const { align, content, direction, dropCap, placeholder } = attributes;
111
+ const { content, direction, dropCap, placeholder, style } = attributes;
112
+ const textAlign = style?.typography?.textAlign;
112
113
  const blockProps = useBlockProps( {
113
114
  ref: useOnEnter( { clientId, content } ),
114
115
  className: clsx( {
115
- 'has-drop-cap': hasDropCapDisabled( align ) ? false : dropCap,
116
- [ `has-text-align-${ align }` ]: align,
116
+ 'has-drop-cap': hasDropCapDisabled( textAlign ) ? false : dropCap,
117
117
  } ),
118
118
  style: { direction },
119
119
  } );
@@ -123,17 +123,6 @@ function ParagraphBlock( {
123
123
  <>
124
124
  { blockEditingMode === 'default' && (
125
125
  <BlockControls group="block">
126
- <AlignmentControl
127
- value={ align }
128
- onChange={ ( newAlign ) =>
129
- setAttributes( {
130
- align: newAlign,
131
- dropCap: hasDropCapDisabled( newAlign )
132
- ? false
133
- : dropCap,
134
- } )
135
- }
136
- />
137
126
  <ParagraphRTLControl
138
127
  direction={ direction }
139
128
  setDirection={ ( newDirection ) =>
@@ -29,7 +29,8 @@ function ParagraphBlock( {
29
29
  return !! select( blockEditorStore ).getSettings().isRTL;
30
30
  }, [] );
31
31
 
32
- const { align, content, placeholder } = attributes;
32
+ const { content, placeholder, style: attributesStyle } = attributes;
33
+ const textAlign = attributesStyle?.typography?.textAlign;
33
34
 
34
35
  const styles = {
35
36
  ...( style?.baseColors && {
@@ -40,9 +41,20 @@ function ParagraphBlock( {
40
41
  ...style,
41
42
  };
42
43
 
43
- const onAlignmentChange = useCallback( ( nextAlign ) => {
44
- setAttributes( { align: nextAlign } );
45
- }, [] );
44
+ const onAlignmentChange = useCallback(
45
+ ( nextAlign ) => {
46
+ setAttributes( {
47
+ style: {
48
+ ...attributesStyle,
49
+ typography: {
50
+ ...attributesStyle?.typography,
51
+ textAlign: nextAlign,
52
+ },
53
+ },
54
+ } );
55
+ },
56
+ [ attributesStyle, setAttributes ]
57
+ );
46
58
 
47
59
  const parentTextAlignment = allowedParentBlockAlignments.includes(
48
60
  parentBlockAlignment
@@ -50,13 +62,13 @@ function ParagraphBlock( {
50
62
  ? parentBlockAlignment
51
63
  : undefined;
52
64
 
53
- const textAlignment = align || parentTextAlignment;
65
+ const textAlignment = textAlign || parentTextAlignment;
54
66
 
55
67
  return (
56
68
  <>
57
69
  <BlockControls group="block">
58
70
  <AlignmentControl
59
- value={ align }
71
+ value={ textAlign }
60
72
  isRTL={ isRTL }
61
73
  onChange={ onAlignmentChange }
62
74
  />
@@ -13,6 +13,7 @@ import edit from './edit';
13
13
  import metadata from './block.json';
14
14
  import save from './save';
15
15
  import transforms from './transforms';
16
+ import variations from './variations';
16
17
 
17
18
  const { name } = metadata;
18
19
 
@@ -54,6 +55,7 @@ export const settings = {
54
55
  },
55
56
  edit,
56
57
  save,
58
+ variations,
57
59
  };
58
60
 
59
61
  export const init = () => initBlock( { name, metadata, settings } );
@@ -10,13 +10,14 @@ import { RichText, useBlockProps } from '@wordpress/block-editor';
10
10
  import { isRTL } from '@wordpress/i18n';
11
11
 
12
12
  export default function save( { attributes } ) {
13
- const { align, content, dropCap, direction } = attributes;
13
+ const { content, dropCap, direction, style } = attributes;
14
+ const textAlign = style?.typography?.textAlign;
14
15
  const className = clsx( {
15
16
  'has-drop-cap':
16
- align === ( isRTL() ? 'left' : 'right' ) || align === 'center'
17
+ textAlign === ( isRTL() ? 'left' : 'right' ) ||
18
+ textAlign === 'center'
17
19
  ? false
18
20
  : dropCap,
19
- [ `has-text-align-${ align }` ]: align,
20
21
  } );
21
22
 
22
23
  return (
@@ -44,12 +44,6 @@ exports[`Paragraph block transforms to Preformatted block 1`] = `
44
44
  <!-- /wp:preformatted -->"
45
45
  `;
46
46
 
47
- exports[`Paragraph block transforms to Pullquote block 1`] = `
48
- "<!-- wp:pullquote -->
49
- <figure class="wp-block-pullquote"><blockquote><p>Example text</p></blockquote></figure>
50
- <!-- /wp:pullquote -->"
51
- `;
52
-
53
47
  exports[`Paragraph block transforms to Quote block 1`] = `
54
48
  "<!-- wp:quote -->
55
49
  <blockquote class="wp-block-quote"><!-- wp:paragraph -->