@wordpress/block-library 9.40.2-next.v.202602271551.0 → 9.41.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 (89) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/button/block.json +11 -3
  3. package/build/button/deprecated.cjs +246 -13
  4. package/build/button/deprecated.cjs.map +2 -2
  5. package/build/button/edit.cjs +45 -58
  6. package/build/button/edit.cjs.map +3 -3
  7. package/build/button/save.cjs +3 -7
  8. package/build/button/save.cjs.map +2 -2
  9. package/build/button/utils.cjs +59 -0
  10. package/build/button/utils.cjs.map +7 -0
  11. package/build/image/image.cjs +1 -1
  12. package/build/image/image.cjs.map +2 -2
  13. package/build/navigation-link/shared/use-link-preview.cjs +2 -2
  14. package/build/navigation-link/shared/use-link-preview.cjs.map +2 -2
  15. package/build/playlist/edit.cjs +43 -136
  16. package/build/playlist/edit.cjs.map +3 -3
  17. package/build/playlist/view.cjs +56 -38
  18. package/build/playlist/view.cjs.map +2 -2
  19. package/build/playlist-track/edit.cjs +0 -1
  20. package/build/playlist-track/edit.cjs.map +2 -2
  21. package/build/post-title/block.json +3 -0
  22. package/build/post-title/edit.cjs +2 -2
  23. package/build/post-title/edit.cjs.map +2 -2
  24. package/build/utils/waveform-player.cjs +68 -0
  25. package/build/utils/waveform-player.cjs.map +7 -0
  26. package/build/utils/waveform-utils.cjs +171 -0
  27. package/build/utils/waveform-utils.cjs.map +7 -0
  28. package/build-module/button/block.json +11 -3
  29. package/build-module/button/deprecated.mjs +246 -13
  30. package/build-module/button/deprecated.mjs.map +2 -2
  31. package/build-module/button/edit.mjs +47 -63
  32. package/build-module/button/edit.mjs.map +2 -2
  33. package/build-module/button/save.mjs +3 -7
  34. package/build-module/button/save.mjs.map +2 -2
  35. package/build-module/button/utils.mjs +33 -0
  36. package/build-module/button/utils.mjs.map +7 -0
  37. package/build-module/image/image.mjs +1 -1
  38. package/build-module/image/image.mjs.map +2 -2
  39. package/build-module/navigation-link/shared/use-link-preview.mjs +2 -2
  40. package/build-module/navigation-link/shared/use-link-preview.mjs.map +2 -2
  41. package/build-module/playlist/edit.mjs +41 -139
  42. package/build-module/playlist/edit.mjs.map +2 -2
  43. package/build-module/playlist/view.mjs +56 -38
  44. package/build-module/playlist/view.mjs.map +2 -2
  45. package/build-module/playlist-track/edit.mjs +0 -1
  46. package/build-module/playlist-track/edit.mjs.map +2 -2
  47. package/build-module/post-title/block.json +3 -0
  48. package/build-module/post-title/edit.mjs +2 -2
  49. package/build-module/post-title/edit.mjs.map +2 -2
  50. package/build-module/utils/waveform-player.mjs +43 -0
  51. package/build-module/utils/waveform-player.mjs.map +7 -0
  52. package/build-module/utils/waveform-utils.mjs +131 -0
  53. package/build-module/utils/waveform-utils.mjs.map +7 -0
  54. package/build-style/button/style-rtl.css +6 -0
  55. package/build-style/button/style.css +6 -0
  56. package/build-style/editor-rtl.css +3 -3
  57. package/build-style/editor.css +3 -3
  58. package/build-style/playlist/editor-rtl.css +3 -3
  59. package/build-style/playlist/editor.css +3 -3
  60. package/build-style/playlist/style-rtl.css +351 -17
  61. package/build-style/playlist/style.css +351 -17
  62. package/build-style/style-rtl.css +357 -17
  63. package/build-style/style.css +357 -17
  64. package/package.json +39 -38
  65. package/src/button/block.json +11 -3
  66. package/src/button/deprecated.js +254 -16
  67. package/src/button/edit.js +50 -61
  68. package/src/button/index.php +68 -0
  69. package/src/button/save.js +2 -8
  70. package/src/button/style.scss +49 -7
  71. package/src/button/test/utils.js +84 -0
  72. package/src/button/utils.js +42 -0
  73. package/src/image/image.js +14 -15
  74. package/src/image/index.php +3 -1
  75. package/src/navigation-link/shared/test/use-link-preview.test.js +9 -0
  76. package/src/navigation-link/shared/use-link-preview.js +6 -9
  77. package/src/playlist/edit.js +60 -154
  78. package/src/playlist/editor.scss +3 -3
  79. package/src/playlist/index.php +15 -40
  80. package/src/playlist/style.scss +34 -27
  81. package/src/playlist/test/edit.js +137 -0
  82. package/src/playlist/view.js +97 -40
  83. package/src/playlist-track/edit.js +0 -1
  84. package/src/post-title/block.json +3 -0
  85. package/src/post-title/edit.js +4 -2
  86. package/src/search/index.php +1 -1
  87. package/src/utils/test/waveform-utils.js +328 -0
  88. package/src/utils/waveform-player.js +77 -0
  89. package/src/utils/waveform-utils.js +232 -0
@@ -26,6 +26,25 @@ import { compose } from '@wordpress/compose';
26
26
  import migrateFontFamily from '../utils/migrate-font-family';
27
27
  import migrateTextAlign from '../utils/migrate-text-align';
28
28
 
29
+ const migrateWidth = ( attributes ) => {
30
+ const { width, ...otherAttributes } = attributes;
31
+
32
+ if ( ! width ) {
33
+ return otherAttributes;
34
+ }
35
+
36
+ return {
37
+ ...otherAttributes,
38
+ style: {
39
+ ...otherAttributes.style,
40
+ dimensions: {
41
+ ...otherAttributes.style?.dimensions,
42
+ width: `${ width }%`,
43
+ },
44
+ },
45
+ };
46
+ };
47
+
29
48
  const migrateBorderRadius = ( attributes ) => {
30
49
  const { borderRadius, ...newAttributes } = attributes;
31
50
  // We have to check old property `borderRadius` and if
@@ -136,6 +155,211 @@ const blockAttributes = {
136
155
  },
137
156
  };
138
157
 
158
+ const v14 = {
159
+ attributes: {
160
+ tagName: {
161
+ type: 'string',
162
+ enum: [ 'a', 'button' ],
163
+ default: 'a',
164
+ },
165
+ type: {
166
+ type: 'string',
167
+ default: 'button',
168
+ },
169
+ url: {
170
+ type: 'string',
171
+ source: 'attribute',
172
+ selector: 'a',
173
+ attribute: 'href',
174
+ role: 'content',
175
+ },
176
+ title: {
177
+ type: 'string',
178
+ source: 'attribute',
179
+ selector: 'a,button',
180
+ attribute: 'title',
181
+ role: 'content',
182
+ },
183
+ text: {
184
+ type: 'rich-text',
185
+ source: 'rich-text',
186
+ selector: 'a,button',
187
+ role: 'content',
188
+ },
189
+ linkTarget: {
190
+ type: 'string',
191
+ source: 'attribute',
192
+ selector: 'a',
193
+ attribute: 'target',
194
+ role: 'content',
195
+ },
196
+ rel: {
197
+ type: 'string',
198
+ source: 'attribute',
199
+ selector: 'a',
200
+ attribute: 'rel',
201
+ role: 'content',
202
+ },
203
+ placeholder: {
204
+ type: 'string',
205
+ },
206
+ backgroundColor: {
207
+ type: 'string',
208
+ },
209
+ textColor: {
210
+ type: 'string',
211
+ },
212
+ gradient: {
213
+ type: 'string',
214
+ },
215
+ width: {
216
+ type: 'number',
217
+ },
218
+ },
219
+ supports: {
220
+ anchor: true,
221
+ splitting: true,
222
+ align: false,
223
+ alignWide: false,
224
+ color: {
225
+ __experimentalSkipSerialization: true,
226
+ gradients: true,
227
+ __experimentalDefaultControls: {
228
+ background: true,
229
+ text: true,
230
+ },
231
+ },
232
+ typography: {
233
+ __experimentalSkipSerialization: [
234
+ 'fontSize',
235
+ 'lineHeight',
236
+ 'textAlign',
237
+ 'fontFamily',
238
+ 'fontWeight',
239
+ 'fontStyle',
240
+ 'textTransform',
241
+ 'textDecoration',
242
+ 'letterSpacing',
243
+ ],
244
+ fontSize: true,
245
+ lineHeight: true,
246
+ textAlign: true,
247
+ __experimentalFontFamily: true,
248
+ __experimentalFontWeight: true,
249
+ __experimentalFontStyle: true,
250
+ __experimentalTextTransform: true,
251
+ __experimentalTextDecoration: true,
252
+ __experimentalLetterSpacing: true,
253
+ __experimentalWritingMode: true,
254
+ __experimentalDefaultControls: {
255
+ fontSize: true,
256
+ },
257
+ },
258
+ reusable: false,
259
+ shadow: {
260
+ __experimentalSkipSerialization: true,
261
+ },
262
+ spacing: {
263
+ __experimentalSkipSerialization: true,
264
+ padding: [ 'horizontal', 'vertical' ],
265
+ __experimentalDefaultControls: {
266
+ padding: true,
267
+ },
268
+ },
269
+ __experimentalBorder: {
270
+ color: true,
271
+ radius: true,
272
+ style: true,
273
+ width: true,
274
+ __experimentalSkipSerialization: true,
275
+ __experimentalDefaultControls: {
276
+ color: true,
277
+ radius: true,
278
+ style: true,
279
+ width: true,
280
+ },
281
+ },
282
+ interactivity: {
283
+ clientNavigation: true,
284
+ },
285
+ },
286
+ selectors: {
287
+ root: '.wp-block-button .wp-block-button__link',
288
+ typography: {
289
+ writingMode: '.wp-block-button',
290
+ },
291
+ },
292
+ save( { attributes, className } ) {
293
+ const {
294
+ tagName,
295
+ type,
296
+ fontSize,
297
+ linkTarget,
298
+ rel,
299
+ style,
300
+ text,
301
+ title,
302
+ url,
303
+ width,
304
+ } = attributes;
305
+
306
+ const TagName = tagName || 'a';
307
+ const isButtonTag = 'button' === TagName;
308
+ const buttonType = type || 'button';
309
+ const borderProps = getBorderClassesAndStyles( attributes );
310
+ const colorProps = getColorClassesAndStyles( attributes );
311
+ const spacingProps = getSpacingClassesAndStyles( attributes );
312
+ const shadowProps = getShadowClassesAndStyles( attributes );
313
+ const typographyProps = getTypographyClassesAndStyles( attributes );
314
+ const buttonClasses = clsx(
315
+ 'wp-block-button__link',
316
+ colorProps.className,
317
+ borderProps.className,
318
+ typographyProps.className,
319
+ {
320
+ // For backwards compatibility add style that isn't
321
+ // provided via block support.
322
+ 'no-border-radius': style?.border?.radius === 0,
323
+ [ `has-custom-font-size` ]:
324
+ fontSize || style?.typography?.fontSize,
325
+ },
326
+ __experimentalGetElementClassName( 'button' )
327
+ );
328
+ const buttonStyle = {
329
+ ...borderProps.style,
330
+ ...colorProps.style,
331
+ ...spacingProps.style,
332
+ ...shadowProps.style,
333
+ ...typographyProps.style,
334
+ writingMode: undefined,
335
+ };
336
+
337
+ const wrapperClasses = clsx( className, {
338
+ [ `has-custom-width wp-block-button__width-${ width }` ]: width,
339
+ } );
340
+
341
+ return (
342
+ <div { ...useBlockProps.save( { className: wrapperClasses } ) }>
343
+ <RichText.Content
344
+ tagName={ TagName }
345
+ type={ isButtonTag ? buttonType : null }
346
+ className={ buttonClasses }
347
+ href={ isButtonTag ? null : url }
348
+ title={ title }
349
+ style={ buttonStyle }
350
+ value={ text }
351
+ target={ isButtonTag ? null : linkTarget }
352
+ rel={ isButtonTag ? null : rel }
353
+ />
354
+ </div>
355
+ );
356
+ },
357
+ isEligible( attributes ) {
358
+ return typeof attributes.width === 'number';
359
+ },
360
+ migrate: migrateWidth,
361
+ };
362
+
139
363
  const v13 = {
140
364
  attributes: {
141
365
  tagName: {
@@ -317,10 +541,6 @@ const v13 = {
317
541
  writingMode: undefined,
318
542
  };
319
543
 
320
- // The use of a `title` attribute here is soft-deprecated, but still applied
321
- // if it had already been assigned, for the sake of backward-compatibility.
322
- // A title will no longer be assigned for new or updated button block links.
323
-
324
544
  const wrapperClasses = clsx( className, {
325
545
  [ `has-custom-width wp-block-button__width-${ width }` ]: width,
326
546
  } );
@@ -342,9 +562,9 @@ const v13 = {
342
562
  );
343
563
  },
344
564
  isEligible( attributes ) {
345
- return !! attributes.textAlign;
565
+ return !! attributes.textAlign || typeof attributes.width === 'number';
346
566
  },
347
- migrate: migrateTextAlign,
567
+ migrate: compose( migrateWidth, migrateTextAlign ),
348
568
  };
349
569
 
350
570
  const v12 = {
@@ -531,6 +751,10 @@ const v12 = {
531
751
  </div>
532
752
  );
533
753
  },
754
+ isEligible( attributes ) {
755
+ return typeof attributes.width === 'number';
756
+ },
757
+ migrate: migrateWidth,
534
758
  };
535
759
 
536
760
  const v11 = {
@@ -793,13 +1017,14 @@ const v10 = {
793
1017
  </div>
794
1018
  );
795
1019
  },
796
- migrate: migrateFontFamily,
797
- isEligible( { style } ) {
798
- return style?.typography?.fontFamily;
1020
+ migrate: compose( migrateWidth, migrateFontFamily ),
1021
+ isEligible( { style, width } ) {
1022
+ return style?.typography?.fontFamily || typeof width === 'number';
799
1023
  },
800
1024
  };
801
1025
 
802
1026
  const deprecated = [
1027
+ v14,
803
1028
  v13,
804
1029
  v12,
805
1030
  v11,
@@ -908,7 +1133,11 @@ const deprecated = [
908
1133
  </div>
909
1134
  );
910
1135
  },
911
- migrate: compose( migrateFontFamily, migrateBorderRadius ),
1136
+ migrate: compose(
1137
+ migrateWidth,
1138
+ migrateFontFamily,
1139
+ migrateBorderRadius
1140
+ ),
912
1141
  },
913
1142
  {
914
1143
  supports: {
@@ -996,7 +1225,11 @@ const deprecated = [
996
1225
  </div>
997
1226
  );
998
1227
  },
999
- migrate: compose( migrateFontFamily, migrateBorderRadius ),
1228
+ migrate: compose(
1229
+ migrateWidth,
1230
+ migrateFontFamily,
1231
+ migrateBorderRadius
1232
+ ),
1000
1233
  },
1001
1234
  {
1002
1235
  supports: {
@@ -1084,7 +1317,11 @@ const deprecated = [
1084
1317
  </div>
1085
1318
  );
1086
1319
  },
1087
- migrate: compose( migrateFontFamily, migrateBorderRadius ),
1320
+ migrate: compose(
1321
+ migrateWidth,
1322
+ migrateFontFamily,
1323
+ migrateBorderRadius
1324
+ ),
1088
1325
  },
1089
1326
  {
1090
1327
  supports: {
@@ -1148,7 +1385,7 @@ const deprecated = [
1148
1385
  />
1149
1386
  );
1150
1387
  },
1151
- migrate: migrateBorderRadius,
1388
+ migrate: compose( migrateWidth, migrateBorderRadius ),
1152
1389
  },
1153
1390
  {
1154
1391
  supports: {
@@ -1200,6 +1437,7 @@ const deprecated = [
1200
1437
  !! attributes.customGradient ||
1201
1438
  !! attributes.align,
1202
1439
  migrate: compose(
1440
+ migrateWidth,
1203
1441
  migrateBorderRadius,
1204
1442
  migrateCustomColorsAndGradients,
1205
1443
  migrateAlign
@@ -1394,7 +1632,7 @@ const deprecated = [
1394
1632
  type: 'string',
1395
1633
  },
1396
1634
  },
1397
- migrate: oldColorsMigration,
1635
+ migrate: compose( migrateWidth, oldColorsMigration ),
1398
1636
  save( { attributes } ) {
1399
1637
  const {
1400
1638
  url,
@@ -1477,7 +1715,7 @@ const deprecated = [
1477
1715
  </div>
1478
1716
  );
1479
1717
  },
1480
- migrate: oldColorsMigration,
1718
+ migrate: compose( migrateWidth, oldColorsMigration ),
1481
1719
  },
1482
1720
  {
1483
1721
  attributes: {
@@ -1511,7 +1749,7 @@ const deprecated = [
1511
1749
  </div>
1512
1750
  );
1513
1751
  },
1514
- migrate: oldColorsMigration,
1752
+ migrate: compose( migrateWidth, oldColorsMigration ),
1515
1753
  },
1516
1754
  ];
1517
1755
 
@@ -12,10 +12,6 @@ import {
12
12
  ToolbarButton,
13
13
  Popover,
14
14
  ExternalLink,
15
- __experimentalToolsPanel as ToolsPanel,
16
- __experimentalToolsPanelItem as ToolsPanelItem,
17
- __experimentalToggleGroupControl as ToggleGroupControl,
18
- __experimentalToggleGroupControlOption as ToggleGroupControlOption,
19
15
  } from '@wordpress/components';
20
16
  import {
21
17
  BlockControls,
@@ -27,6 +23,7 @@ import {
27
23
  __experimentalUseColorProps as useColorProps,
28
24
  __experimentalGetSpacingClassesAndStyles as useSpacingProps,
29
25
  __experimentalGetShadowClassesAndStyles as useShadowProps,
26
+ __experimentalGetDimensionsClassesAndStyles as useDimensionsProps,
30
27
  __experimentalGetElementClassName,
31
28
  store as blockEditorStore,
32
29
  useBlockEditingMode,
@@ -47,9 +44,9 @@ import { useSelect, useDispatch } from '@wordpress/data';
47
44
  import { NEW_TAB_TARGET, NOFOLLOW_REL } from './constants';
48
45
  import { getUpdatedLinkAttributes } from './get-updated-link-attributes';
49
46
  import removeAnchorTag from '../utils/remove-anchor-tag';
50
- import { useToolsPanelDropdownMenuProps } from '../utils/hooks';
51
47
  import { unlock } from '../lock-unlock';
52
48
  import useDeprecatedTextAlign from '../utils/deprecated-text-align-attributes';
49
+ import { getWidthClasses, isPercentageWidth } from './utils';
53
50
 
54
51
  const { HTMLElementControl } = unlock( blockEditorPrivateApis );
55
52
 
@@ -117,49 +114,6 @@ function useEnter( props ) {
117
114
  }, [] );
118
115
  }
119
116
 
120
- function WidthPanel( { selectedWidth, setAttributes } ) {
121
- const dropdownMenuProps = useToolsPanelDropdownMenuProps();
122
-
123
- return (
124
- <ToolsPanel
125
- label={ __( 'Settings' ) }
126
- resetAll={ () => setAttributes( { width: undefined } ) }
127
- dropdownMenuProps={ dropdownMenuProps }
128
- >
129
- <ToolsPanelItem
130
- label={ __( 'Width' ) }
131
- isShownByDefault
132
- hasValue={ () => !! selectedWidth }
133
- onDeselect={ () => setAttributes( { width: undefined } ) }
134
- >
135
- <ToggleGroupControl
136
- label={ __( 'Width' ) }
137
- value={ selectedWidth }
138
- onChange={ ( newWidth ) =>
139
- setAttributes( { width: newWidth } )
140
- }
141
- isBlock
142
- __next40pxDefaultSize
143
- >
144
- { [ 25, 50, 75, 100 ].map( ( widthValue ) => {
145
- return (
146
- <ToggleGroupControlOption
147
- key={ widthValue }
148
- value={ widthValue }
149
- label={ sprintf(
150
- /* translators: %d: Percentage value. */
151
- __( '%d%%' ),
152
- widthValue
153
- ) }
154
- />
155
- );
156
- } ) }
157
- </ToggleGroupControl>
158
- </ToolsPanelItem>
159
- </ToolsPanel>
160
- );
161
- }
162
-
163
117
  function ButtonEdit( props ) {
164
118
  const {
165
119
  attributes,
@@ -179,9 +133,10 @@ function ButtonEdit( props ) {
179
133
  style,
180
134
  text,
181
135
  url,
182
- width,
183
136
  metadata,
184
137
  } = attributes;
138
+ const width = style?.dimensions?.width;
139
+
185
140
  useDeprecatedTextAlign( props );
186
141
 
187
142
  const TagName = tagName || 'a';
@@ -203,6 +158,7 @@ function ButtonEdit( props ) {
203
158
  const colorProps = useColorProps( attributes );
204
159
  const spacingProps = useSpacingProps( attributes );
205
160
  const shadowProps = useShadowProps( attributes );
161
+ const dimensionsProps = useDimensionsProps( attributes );
206
162
  const ref = useRef();
207
163
  const richTextRef = useRef();
208
164
  const blockProps = useBlockProps( {
@@ -304,10 +260,21 @@ function ButtonEdit( props ) {
304
260
  const useEnterRef = useEnter( { content: text, clientId } );
305
261
  const mergedRef = useMergeRefs( [ useEnterRef, richTextRef ] );
306
262
 
307
- const [ fluidTypographySettings, layout ] = useSettings(
263
+ const [ fluidTypographySettings, layout, dimensionSizes ] = useSettings(
308
264
  'typography.fluid',
309
- 'layout'
265
+ 'layout',
266
+ 'dimensions.dimensionSizes'
310
267
  );
268
+ const dimensionPresets = useMemo( () => {
269
+ if ( ! dimensionSizes ) {
270
+ return [];
271
+ }
272
+ return [
273
+ ...( dimensionSizes?.custom ?? [] ),
274
+ ...( dimensionSizes?.theme ?? [] ),
275
+ ...( dimensionSizes?.default ?? [] ),
276
+ ];
277
+ }, [ dimensionSizes ] );
311
278
  const typographyProps = useTypographyProps( attributes, {
312
279
  typography: {
313
280
  fluid: fluidTypographySettings,
@@ -317,18 +284,46 @@ function ButtonEdit( props ) {
317
284
  },
318
285
  } );
319
286
 
287
+ // Resolve preset dimension references to their actual values.
288
+ const resolvedWidth = useMemo( () => {
289
+ if ( ! width ) {
290
+ return undefined;
291
+ }
292
+ const presetPrefix = 'var:preset|dimension|';
293
+ if ( width.startsWith( presetPrefix ) ) {
294
+ const slug = width.slice( presetPrefix.length );
295
+ const preset = dimensionPresets?.find( ( p ) => p.slug === slug );
296
+ return preset?.size ?? width;
297
+ }
298
+ return width;
299
+ }, [ width, dimensionPresets ] );
300
+
320
301
  const hasNonContentControls = blockEditingMode === 'default';
321
302
  const hasBlockControls =
322
303
  hasNonContentControls || ( isLinkTag && ! lockUrlControls );
304
+ const classes = clsx(
305
+ blockProps.className,
306
+ getWidthClasses( resolvedWidth )
307
+ );
308
+
309
+ const widthStyle = useMemo( () => {
310
+ if ( ! width ) {
311
+ return {};
312
+ }
313
+ if ( isPercentageWidth( resolvedWidth ) ) {
314
+ return {
315
+ '--wp--block-button--width': parseFloat( resolvedWidth ),
316
+ };
317
+ }
318
+ return dimensionsProps.style;
319
+ }, [ width, resolvedWidth, dimensionsProps.style ] );
323
320
 
324
321
  return (
325
322
  <>
326
323
  <div
327
324
  { ...blockProps }
328
- className={ clsx( blockProps.className, {
329
- [ `has-custom-width wp-block-button__width-${ width }` ]:
330
- width,
331
- } ) }
325
+ className={ classes }
326
+ style={ { ...blockProps.style, ...widthStyle } }
332
327
  >
333
328
  <RichText
334
329
  ref={ mergedRef }
@@ -432,12 +427,6 @@ function ButtonEdit( props ) {
432
427
  />
433
428
  </Popover>
434
429
  ) }
435
- <InspectorControls>
436
- <WidthPanel
437
- selectedWidth={ width }
438
- setAttributes={ setAttributes }
439
- />
440
- </InspectorControls>
441
430
  <InspectorControls group="advanced">
442
431
  <HTMLElementControl
443
432
  tagName={ tagName }
@@ -60,6 +60,74 @@ function render_block_core_button( $attributes, $content ) {
60
60
  return '';
61
61
  }
62
62
 
63
+ $width = $attributes['style']['dimensions']['width'] ?? null;
64
+
65
+ if ( $width ) {
66
+ // Resolve preset references to their actual values.
67
+ $resolved_width = $width;
68
+ $is_preset = str_starts_with( $width, 'var:preset|dimension|' );
69
+
70
+ if ( $is_preset ) {
71
+ $slug = substr( $width, strlen( 'var:preset|dimension|' ) );
72
+ $dimension_presets = wp_get_global_settings(
73
+ array( 'dimensions', 'dimensionSizes' ),
74
+ array( 'block_name' => 'core/button' )
75
+ );
76
+
77
+ // Search origins in priority order: custom > theme > default.
78
+ if ( is_array( $dimension_presets ) ) {
79
+ foreach ( array( 'custom', 'theme', 'default' ) as $origin ) {
80
+ if ( empty( $dimension_presets[ $origin ] ) || ! is_array( $dimension_presets[ $origin ] ) ) {
81
+ continue;
82
+ }
83
+ foreach ( $dimension_presets[ $origin ] as $preset ) {
84
+ if ( isset( $preset['slug'] ) && $preset['slug'] === $slug ) {
85
+ $resolved_width = $preset['size'] ?? $width;
86
+ break 2;
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+
93
+ $is_percentage = str_ends_with( $resolved_width, '%' );
94
+
95
+ $processor = new WP_HTML_Tag_Processor( $content );
96
+ // Target the outer wrapper div.
97
+ if ( $processor->next_tag( array( 'class_name' => 'wp-block-button' ) ) ) {
98
+ $processor->add_class( 'has-custom-width' );
99
+ $existing_style = $processor->get_attribute( 'style' );
100
+ $existing_style = is_string( $existing_style ) ? $existing_style : '';
101
+
102
+ if ( $is_percentage ) {
103
+ $numeric_width = (float) $resolved_width;
104
+ $processor->add_class( 'wp-block-button__width' );
105
+
106
+ // Maintain legacy class for the standard percentage widths.
107
+ $legacy_widths = array(
108
+ '25%' => 'wp-block-button__width-25',
109
+ '50%' => 'wp-block-button__width-50',
110
+ '75%' => 'wp-block-button__width-75',
111
+ '100%' => 'wp-block-button__width-100',
112
+ );
113
+ if ( isset( $legacy_widths[ $resolved_width ] ) ) {
114
+ $processor->add_class( $legacy_widths[ $resolved_width ] );
115
+ }
116
+
117
+ $width_style = "--wp--block-button--width: $numeric_width;";
118
+ $processor->set_attribute( 'style', $width_style . ( $existing_style ? ' ' . $existing_style : '' ) );
119
+ } else {
120
+ $css_value = $is_preset
121
+ ? 'var(--wp--preset--dimension--' . _wp_to_kebab_case( $slug ) . ')'
122
+ : $width;
123
+ $width_style = "width: $css_value;";
124
+ $processor->set_attribute( 'style', $width_style . ( $existing_style ? ' ' . $existing_style : '' ) );
125
+ }
126
+
127
+ $content = $processor->get_updated_html();
128
+ }
129
+ }
130
+
63
131
  return $content;
64
132
  }
65
133
 
@@ -17,7 +17,7 @@ import {
17
17
  getTypographyClassesAndStyles,
18
18
  } from '@wordpress/block-editor';
19
19
 
20
- export default function save( { attributes, className } ) {
20
+ export default function save( { attributes } ) {
21
21
  const {
22
22
  tagName,
23
23
  type,
@@ -28,9 +28,7 @@ export default function save( { attributes, className } ) {
28
28
  text,
29
29
  title,
30
30
  url,
31
- width,
32
31
  } = attributes;
33
-
34
32
  const TagName = tagName || 'a';
35
33
  const isButtonTag = 'button' === TagName;
36
34
  const buttonType = type || 'button';
@@ -65,12 +63,8 @@ export default function save( { attributes, className } ) {
65
63
  // if it had already been assigned, for the sake of backward-compatibility.
66
64
  // A title will no longer be assigned for new or updated button block links.
67
65
 
68
- const wrapperClasses = clsx( className, {
69
- [ `has-custom-width wp-block-button__width-${ width }` ]: width,
70
- } );
71
-
72
66
  return (
73
- <div { ...useBlockProps.save( { className: wrapperClasses } ) }>
67
+ <div { ...useBlockProps.save() }>
74
68
  <RichText.Content
75
69
  tagName={ TagName }
76
70
  type={ isButtonTag ? buttonType : null }