@wordpress/block-editor 15.6.5 → 15.6.7

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 (46) hide show
  1. package/build/components/block-variation-transforms/index.js +32 -5
  2. package/build/components/block-variation-transforms/index.js.map +2 -2
  3. package/build/components/border-radius-control/single-input-control.js +1 -0
  4. package/build/components/border-radius-control/single-input-control.js.map +2 -2
  5. package/build/components/global-styles/typography-panel.js +3 -2
  6. package/build/components/global-styles/typography-panel.js.map +2 -2
  7. package/build/hooks/block-bindings.js +11 -7
  8. package/build/hooks/block-bindings.js.map +2 -2
  9. package/build/hooks/fit-text.js +3 -71
  10. package/build/hooks/fit-text.js.map +3 -3
  11. package/build/hooks/font-size.js +5 -2
  12. package/build/hooks/font-size.js.map +2 -2
  13. package/build/hooks/typography.js +3 -7
  14. package/build/hooks/typography.js.map +2 -2
  15. package/build/store/selectors.js +9 -3
  16. package/build/store/selectors.js.map +2 -2
  17. package/build/utils/fit-text-utils.js +10 -2
  18. package/build/utils/fit-text-utils.js.map +2 -2
  19. package/build-module/components/block-variation-transforms/index.js +32 -5
  20. package/build-module/components/block-variation-transforms/index.js.map +2 -2
  21. package/build-module/components/border-radius-control/single-input-control.js +1 -0
  22. package/build-module/components/border-radius-control/single-input-control.js.map +2 -2
  23. package/build-module/components/global-styles/typography-panel.js +3 -2
  24. package/build-module/components/global-styles/typography-panel.js.map +2 -2
  25. package/build-module/hooks/block-bindings.js +11 -7
  26. package/build-module/hooks/block-bindings.js.map +2 -2
  27. package/build-module/hooks/fit-text.js +2 -62
  28. package/build-module/hooks/fit-text.js.map +2 -2
  29. package/build-module/hooks/font-size.js +5 -2
  30. package/build-module/hooks/font-size.js.map +2 -2
  31. package/build-module/hooks/typography.js +3 -7
  32. package/build-module/hooks/typography.js.map +2 -2
  33. package/build-module/store/selectors.js +9 -3
  34. package/build-module/store/selectors.js.map +2 -2
  35. package/build-module/utils/fit-text-utils.js +10 -2
  36. package/build-module/utils/fit-text-utils.js.map +2 -2
  37. package/package.json +8 -8
  38. package/src/components/block-variation-transforms/index.js +96 -35
  39. package/src/components/border-radius-control/single-input-control.js +1 -0
  40. package/src/components/global-styles/typography-panel.js +2 -1
  41. package/src/hooks/block-bindings.js +11 -7
  42. package/src/hooks/fit-text.js +2 -78
  43. package/src/hooks/font-size.js +7 -2
  44. package/src/hooks/typography.js +2 -10
  45. package/src/store/selectors.js +17 -3
  46. package/src/utils/fit-text-utils.js +20 -2
@@ -1,12 +1,19 @@
1
1
  function findOptimalFontSize(textElement, applyFontSize) {
2
2
  const alreadyHasScrollableHeight = textElement.scrollHeight > textElement.clientHeight;
3
3
  let minSize = 5;
4
- let maxSize = 600;
4
+ let maxSize = 2400;
5
5
  let bestSize = minSize;
6
+ const computedStyle = window.getComputedStyle(textElement);
7
+ const paddingLeft = parseFloat(computedStyle.paddingLeft) || 0;
8
+ const paddingRight = parseFloat(computedStyle.paddingRight) || 0;
9
+ const range = document.createRange();
10
+ range.selectNodeContents(textElement);
6
11
  while (minSize <= maxSize) {
7
12
  const midSize = Math.floor((minSize + maxSize) / 2);
8
13
  applyFontSize(midSize);
9
- const fitsWidth = textElement.scrollWidth <= textElement.clientWidth;
14
+ const rect = range.getBoundingClientRect();
15
+ const textWidth = rect.width;
16
+ const fitsWidth = textElement.scrollWidth <= textElement.clientWidth && textWidth <= textElement.clientWidth - paddingLeft - paddingRight;
10
17
  const fitsHeight = alreadyHasScrollableHeight || textElement.scrollHeight <= textElement.clientHeight;
11
18
  if (fitsWidth && fitsHeight) {
12
19
  bestSize = midSize;
@@ -15,6 +22,7 @@ function findOptimalFontSize(textElement, applyFontSize) {
15
22
  maxSize = midSize - 1;
16
23
  }
17
24
  }
25
+ range.detach();
18
26
  return bestSize;
19
27
  }
20
28
  function optimizeFitText(textElement, applyFontSize) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/fit-text-utils.js"],
4
- "sourcesContent": ["/**\n * Shared utility functions for fit text functionality.\n * Uses callback-based approach for maximum code reuse between editor and frontend.\n */\n\n/**\n * Find optimal font size using simple binary search between 5-600px.\n *\n * @param {HTMLElement} textElement The text element\n * @param {Function} applyFontSize Function that receives font size in pixels\n * @return {number} Optimal font size\n */\nfunction findOptimalFontSize( textElement, applyFontSize ) {\n\tconst alreadyHasScrollableHeight =\n\t\ttextElement.scrollHeight > textElement.clientHeight;\n\tlet minSize = 5;\n\tlet maxSize = 600;\n\tlet bestSize = minSize;\n\n\twhile ( minSize <= maxSize ) {\n\t\tconst midSize = Math.floor( ( minSize + maxSize ) / 2 );\n\t\tapplyFontSize( midSize );\n\n\t\tconst fitsWidth = textElement.scrollWidth <= textElement.clientWidth;\n\t\tconst fitsHeight =\n\t\t\talreadyHasScrollableHeight ||\n\t\t\ttextElement.scrollHeight <= textElement.clientHeight;\n\n\t\tif ( fitsWidth && fitsHeight ) {\n\t\t\tbestSize = midSize;\n\t\t\tminSize = midSize + 1;\n\t\t} else {\n\t\t\tmaxSize = midSize - 1;\n\t\t}\n\t}\n\n\treturn bestSize;\n}\n\n/**\n * Complete fit text optimization for a single text element.\n * Handles the full flow using callbacks for font size application.\n *\n * @param {HTMLElement} textElement The text element (paragraph, heading, etc.)\n * @param {Function} applyFontSize Function that receives font size in pixels (0 to clear, >0 to apply)\n */\nexport function optimizeFitText( textElement, applyFontSize ) {\n\tif ( ! textElement ) {\n\t\treturn;\n\t}\n\n\tapplyFontSize( 0 );\n\n\tconst optimalSize = findOptimalFontSize( textElement, applyFontSize );\n\n\tapplyFontSize( optimalSize );\n\treturn optimalSize;\n}\n"],
5
- "mappings": "AAYA,SAAS,oBAAqB,aAAa,eAAgB;AAC1D,QAAM,6BACL,YAAY,eAAe,YAAY;AACxC,MAAI,UAAU;AACd,MAAI,UAAU;AACd,MAAI,WAAW;AAEf,SAAQ,WAAW,SAAU;AAC5B,UAAM,UAAU,KAAK,OAAS,UAAU,WAAY,CAAE;AACtD,kBAAe,OAAQ;AAEvB,UAAM,YAAY,YAAY,eAAe,YAAY;AACzD,UAAM,aACL,8BACA,YAAY,gBAAgB,YAAY;AAEzC,QAAK,aAAa,YAAa;AAC9B,iBAAW;AACX,gBAAU,UAAU;AAAA,IACrB,OAAO;AACN,gBAAU,UAAU;AAAA,IACrB;AAAA,EACD;AAEA,SAAO;AACR;AASO,SAAS,gBAAiB,aAAa,eAAgB;AAC7D,MAAK,CAAE,aAAc;AACpB;AAAA,EACD;AAEA,gBAAe,CAAE;AAEjB,QAAM,cAAc,oBAAqB,aAAa,aAAc;AAEpE,gBAAe,WAAY;AAC3B,SAAO;AACR;",
4
+ "sourcesContent": ["/**\n * Shared utility functions for fit text functionality.\n * Uses callback-based approach for maximum code reuse between editor and frontend.\n */\n\n/**\n * Find optimal font size using simple binary search between 5-600px.\n *\n * @param {HTMLElement} textElement The text element\n * @param {Function} applyFontSize Function that receives font size in pixels\n * @return {number} Optimal font size\n */\nfunction findOptimalFontSize( textElement, applyFontSize ) {\n\tconst alreadyHasScrollableHeight =\n\t\ttextElement.scrollHeight > textElement.clientHeight;\n\tlet minSize = 5;\n\tlet maxSize = 2400;\n\tlet bestSize = minSize;\n\n\tconst computedStyle = window.getComputedStyle( textElement );\n\tconst paddingLeft = parseFloat( computedStyle.paddingLeft ) || 0;\n\tconst paddingRight = parseFloat( computedStyle.paddingRight ) || 0;\n\tconst range = document.createRange();\n\trange.selectNodeContents( textElement );\n\n\twhile ( minSize <= maxSize ) {\n\t\tconst midSize = Math.floor( ( minSize + maxSize ) / 2 );\n\t\tapplyFontSize( midSize );\n\n\t\t// When there is padding if the text overflows to the\n\t\t// padding area, it should be considered overflowing.\n\t\t// Use Range API to measure actual text content dimensions.\n\t\tconst rect = range.getBoundingClientRect();\n\t\tconst textWidth = rect.width;\n\n\t\t// Check if text fits within the element's width and is not\n\t\t// overflowing into the padding area.\n\t\tconst fitsWidth =\n\t\t\ttextElement.scrollWidth <= textElement.clientWidth &&\n\t\t\ttextWidth <= textElement.clientWidth - paddingLeft - paddingRight;\n\t\t// Check if text fits within the element's height.\n\t\tconst fitsHeight =\n\t\t\talreadyHasScrollableHeight ||\n\t\t\ttextElement.scrollHeight <= textElement.clientHeight;\n\n\t\tif ( fitsWidth && fitsHeight ) {\n\t\t\tbestSize = midSize;\n\t\t\tminSize = midSize + 1;\n\t\t} else {\n\t\t\tmaxSize = midSize - 1;\n\t\t}\n\t}\n\trange.detach();\n\n\treturn bestSize;\n}\n\n/**\n * Complete fit text optimization for a single text element.\n * Handles the full flow using callbacks for font size application.\n *\n * @param {HTMLElement} textElement The text element (paragraph, heading, etc.)\n * @param {Function} applyFontSize Function that receives font size in pixels (0 to clear, >0 to apply)\n */\nexport function optimizeFitText( textElement, applyFontSize ) {\n\tif ( ! textElement ) {\n\t\treturn;\n\t}\n\n\tapplyFontSize( 0 );\n\n\tconst optimalSize = findOptimalFontSize( textElement, applyFontSize );\n\n\tapplyFontSize( optimalSize );\n\treturn optimalSize;\n}\n"],
5
+ "mappings": "AAYA,SAAS,oBAAqB,aAAa,eAAgB;AAC1D,QAAM,6BACL,YAAY,eAAe,YAAY;AACxC,MAAI,UAAU;AACd,MAAI,UAAU;AACd,MAAI,WAAW;AAEf,QAAM,gBAAgB,OAAO,iBAAkB,WAAY;AAC3D,QAAM,cAAc,WAAY,cAAc,WAAY,KAAK;AAC/D,QAAM,eAAe,WAAY,cAAc,YAAa,KAAK;AACjE,QAAM,QAAQ,SAAS,YAAY;AACnC,QAAM,mBAAoB,WAAY;AAEtC,SAAQ,WAAW,SAAU;AAC5B,UAAM,UAAU,KAAK,OAAS,UAAU,WAAY,CAAE;AACtD,kBAAe,OAAQ;AAKvB,UAAM,OAAO,MAAM,sBAAsB;AACzC,UAAM,YAAY,KAAK;AAIvB,UAAM,YACL,YAAY,eAAe,YAAY,eACvC,aAAa,YAAY,cAAc,cAAc;AAEtD,UAAM,aACL,8BACA,YAAY,gBAAgB,YAAY;AAEzC,QAAK,aAAa,YAAa;AAC9B,iBAAW;AACX,gBAAU,UAAU;AAAA,IACrB,OAAO;AACN,gBAAU,UAAU;AAAA,IACrB;AAAA,EACD;AACA,QAAM,OAAO;AAEb,SAAO;AACR;AASO,SAAS,gBAAiB,aAAa,eAAgB;AAC7D,MAAK,CAAE,aAAc;AACpB;AAAA,EACD;AAEA,gBAAe,CAAE;AAEjB,QAAM,cAAc,oBAAqB,aAAa,aAAc;AAEpE,gBAAe,WAAY;AAC3B,SAAO;AACR;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/block-editor",
3
- "version": "15.6.5",
3
+ "version": "15.6.7",
4
4
  "description": "Generic block editor.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -61,9 +61,9 @@
61
61
  "@wordpress/base-styles": "^6.9.1",
62
62
  "@wordpress/blob": "^4.33.1",
63
63
  "@wordpress/block-serialization-default-parser": "^5.33.1",
64
- "@wordpress/blocks": "^15.6.1",
65
- "@wordpress/commands": "^1.33.3",
66
- "@wordpress/components": "^30.6.3",
64
+ "@wordpress/blocks": "^15.6.2",
65
+ "@wordpress/commands": "^1.33.4",
66
+ "@wordpress/components": "^30.6.4",
67
67
  "@wordpress/compose": "^7.33.1",
68
68
  "@wordpress/data": "^10.33.1",
69
69
  "@wordpress/date": "^5.33.1",
@@ -80,13 +80,13 @@
80
80
  "@wordpress/keyboard-shortcuts": "^5.33.1",
81
81
  "@wordpress/keycodes": "^4.33.1",
82
82
  "@wordpress/notices": "^5.33.1",
83
- "@wordpress/preferences": "^4.33.3",
83
+ "@wordpress/preferences": "^4.33.4",
84
84
  "@wordpress/priority-queue": "^3.33.1",
85
85
  "@wordpress/private-apis": "^1.33.1",
86
- "@wordpress/rich-text": "^7.33.1",
86
+ "@wordpress/rich-text": "^7.33.2",
87
87
  "@wordpress/style-engine": "^2.33.1",
88
88
  "@wordpress/token-list": "^3.33.1",
89
- "@wordpress/upload-media": "^0.18.3",
89
+ "@wordpress/upload-media": "^0.18.4",
90
90
  "@wordpress/url": "^4.33.1",
91
91
  "@wordpress/warning": "^3.33.1",
92
92
  "@wordpress/wordcount": "^4.33.1",
@@ -112,5 +112,5 @@
112
112
  "publishConfig": {
113
113
  "access": "public"
114
114
  },
115
- "gitHead": "36643498e343222da980e5ae235d2f8630ad6b4f"
115
+ "gitHead": "1c473d047a0b8f529f07cc1e23324a6be723bbc9"
116
116
  }
@@ -139,41 +139,102 @@ function VariationsToggleGroupControl( {
139
139
 
140
140
  function __experimentalBlockVariationTransforms( { blockClientId } ) {
141
141
  const { updateBlockAttributes } = useDispatch( blockEditorStore );
142
- const { activeBlockVariation, variations, isContentOnly, isSection } =
143
- useSelect(
144
- ( select ) => {
145
- const { getActiveBlockVariation, getBlockVariations } =
146
- select( blocksStore );
147
-
148
- const {
149
- getBlockName,
150
- getBlockAttributes,
151
- getBlockEditingMode,
152
- isSectionBlock,
153
- } = unlock( select( blockEditorStore ) );
154
-
155
- const name = blockClientId && getBlockName( blockClientId );
156
-
157
- const { hasContentRoleAttribute } = unlock(
158
- select( blocksStore )
159
- );
160
- const isContentBlock = hasContentRoleAttribute( name );
161
-
162
- return {
163
- activeBlockVariation: getActiveBlockVariation(
164
- name,
165
- getBlockAttributes( blockClientId ),
166
- 'transform'
167
- ),
168
- variations: name && getBlockVariations( name, 'transform' ),
169
- isContentOnly:
170
- getBlockEditingMode( blockClientId ) ===
171
- 'contentOnly' && ! isContentBlock,
172
- isSection: isSectionBlock( blockClientId ),
173
- };
174
- },
175
- [ blockClientId ]
176
- );
142
+ const {
143
+ activeBlockVariation,
144
+ unfilteredVariations,
145
+ blockName,
146
+ isContentOnly,
147
+ isSection,
148
+ } = useSelect(
149
+ ( select ) => {
150
+ const { getActiveBlockVariation, getBlockVariations } =
151
+ select( blocksStore );
152
+
153
+ const {
154
+ getBlockName,
155
+ getBlockAttributes,
156
+ getBlockEditingMode,
157
+ isSectionBlock,
158
+ } = unlock( select( blockEditorStore ) );
159
+
160
+ const name = blockClientId && getBlockName( blockClientId );
161
+
162
+ const { hasContentRoleAttribute } = unlock( select( blocksStore ) );
163
+ const isContentBlock = hasContentRoleAttribute( name );
164
+
165
+ return {
166
+ activeBlockVariation: getActiveBlockVariation(
167
+ name,
168
+ getBlockAttributes( blockClientId ),
169
+ 'transform'
170
+ ),
171
+ unfilteredVariations:
172
+ name && getBlockVariations( name, 'transform' ),
173
+ blockName: name,
174
+ isContentOnly:
175
+ getBlockEditingMode( blockClientId ) === 'contentOnly' &&
176
+ ! isContentBlock,
177
+ isSection: isSectionBlock( blockClientId ),
178
+ };
179
+ },
180
+ [ blockClientId ]
181
+ );
182
+
183
+ /*
184
+ * Hack for WordPress 6.9
185
+ *
186
+ * The Stretchy blocks shipped in 6.9 were ultimately
187
+ * implemented as block variations of the base types Paragraph
188
+ * and Heading. See #73056 for discussion and trade-offs.
189
+ *
190
+ * The main drawback of this choice is that the Variations API
191
+ * doesn't offer enough control over how prominent and how tied
192
+ * to the base type a variation should be.
193
+ *
194
+ * In order to ship these new "blocks" with an acceptable UX,
195
+ * we need two hacks until the Variations API is improved:
196
+ *
197
+ * - Don't show the variations switcher in the block inspector
198
+ * for Paragraph, Heading, Stretchy Paragraph and Stretchy
199
+ * Heading (implemented below). Transformations are still
200
+ * available in the block switcher.
201
+ *
202
+ * - Move the stretchy variations to the end of the core blocks
203
+ * list in the block inserter (implemented in
204
+ * getInserterItems in #73056).
205
+ */
206
+ const variations = useMemo( () => {
207
+ if ( blockName === 'core/paragraph' ) {
208
+ // Always hide options when active variation is stretchy, but
209
+ // ensure that there are no third-party variations before doing the
210
+ // same elsewhere.
211
+ if (
212
+ activeBlockVariation?.name === 'stretchy-paragraph' ||
213
+ unfilteredVariations.every( ( v ) =>
214
+ [ 'paragraph', 'stretchy-paragraph' ].includes( v.name )
215
+ )
216
+ ) {
217
+ return [];
218
+ }
219
+ // If there are other variations, only hide the stretchy one.
220
+ return unfilteredVariations.filter(
221
+ ( v ) => v.name !== 'stretchy-paragraph'
222
+ );
223
+ } else if ( blockName === 'core/heading' ) {
224
+ if (
225
+ activeBlockVariation?.name === 'stretchy-heading' ||
226
+ unfilteredVariations.every( ( v ) =>
227
+ [ 'heading', 'stretchy-heading' ].includes( v.name )
228
+ )
229
+ ) {
230
+ return [];
231
+ }
232
+ return unfilteredVariations.filter(
233
+ ( v ) => v.name !== 'stretchy-heading'
234
+ );
235
+ }
236
+ return unfilteredVariations;
237
+ }, [ activeBlockVariation?.name, blockName, unfilteredVariations ] );
177
238
 
178
239
  const selectedValue = activeBlockVariation?.name;
179
240
 
@@ -74,6 +74,7 @@ export default function SingleInputControl( {
74
74
  const onChangeUnit = ( next ) => {
75
75
  const newUnits = { ...selectedUnits };
76
76
  if ( corner === 'all' ) {
77
+ newUnits.flat = next;
77
78
  newUnits.topLeft = next;
78
79
  newUnits.topRight = next;
79
80
  newUnits.bottomLeft = next;
@@ -178,6 +178,7 @@ export default function TypographyPanel( {
178
178
  settings,
179
179
  panelId,
180
180
  defaultControls = DEFAULT_CONTROLS,
181
+ fitText = false,
181
182
  } ) {
182
183
  const decodeValue = ( rawValue ) =>
183
184
  getValueFromVariable( { settings }, '', rawValue );
@@ -447,7 +448,7 @@ export default function TypographyPanel( {
447
448
  />
448
449
  </ToolsPanelItem>
449
450
  ) }
450
- { hasFontSizeEnabled && (
451
+ { hasFontSizeEnabled && ! fitText && (
451
452
  <ToolsPanelItem
452
453
  label={ __( 'Size' ) }
453
454
  hasValue={ hasFontSize }
@@ -110,13 +110,17 @@ function BlockBindingsPanelMenuContent( { attribute, binding, sources } ) {
110
110
  key: item.key,
111
111
  },
112
112
  };
113
- const values = source.getValues( {
114
- select,
115
- context: blockContext,
116
- bindings: {
117
- [ attribute ]: itemBindings,
118
- },
119
- } );
113
+ let values = {};
114
+ try {
115
+ values = source.getValues( {
116
+ select,
117
+ context: blockContext,
118
+ bindings: {
119
+ [ attribute ]: itemBindings,
120
+ },
121
+ } );
122
+ } catch ( e ) {}
123
+
120
124
  return (
121
125
  <Menu.CheckboxItem
122
126
  key={
@@ -5,11 +5,6 @@ import { addFilter } from '@wordpress/hooks';
5
5
  import { hasBlockSupport } from '@wordpress/blocks';
6
6
  import { useEffect, useCallback } from '@wordpress/element';
7
7
  import { useSelect } from '@wordpress/data';
8
- import { __ } from '@wordpress/i18n';
9
- import {
10
- ToggleControl,
11
- __experimentalToolsPanelItem as ToolsPanelItem,
12
- } from '@wordpress/components';
13
8
 
14
9
  const EMPTY_OBJECT = {};
15
10
 
@@ -19,7 +14,6 @@ const EMPTY_OBJECT = {};
19
14
  import { optimizeFitText } from '../utils/fit-text-utils';
20
15
  import { store as blockEditorStore } from '../store';
21
16
  import { useBlockElement } from '../components/block-list/use-block-props/use-block-refs';
22
- import InspectorControls from '../components/inspector-controls';
23
17
 
24
18
  export const FIT_TEXT_SUPPORT_KEY = 'typography.fitText';
25
19
 
@@ -207,76 +201,6 @@ function useFitText( { fitText, name, clientId } ) {
207
201
  ] );
208
202
  }
209
203
 
210
- /**
211
- * Fit text control component for the typography panel.
212
- *
213
- * @param {Object} props Component props.
214
- * @param {string} props.clientId Block client ID.
215
- * @param {Function} props.setAttributes Function to set block attributes.
216
- * @param {string} props.name Block name.
217
- * @param {boolean} props.fitText Whether fit text is enabled.
218
- * @param {string} props.fontSize Font size slug.
219
- * @param {Object} props.style Block style object.
220
- */
221
- export function FitTextControl( {
222
- clientId,
223
- fitText = false,
224
- setAttributes,
225
- name,
226
- fontSize,
227
- style,
228
- } ) {
229
- if ( ! hasBlockSupport( name, FIT_TEXT_SUPPORT_KEY ) ) {
230
- return null;
231
- }
232
- return (
233
- <InspectorControls group="typography">
234
- <ToolsPanelItem
235
- hasValue={ () => fitText }
236
- label={ __( 'Fit text' ) }
237
- onDeselect={ () => setAttributes( { fitText: undefined } ) }
238
- resetAllFilter={ () => ( { fitText: undefined } ) }
239
- panelId={ clientId }
240
- >
241
- <ToggleControl
242
- __nextHasNoMarginBottom
243
- label={ __( 'Fit text' ) }
244
- checked={ fitText }
245
- onChange={ () => {
246
- const newFitText = ! fitText || undefined;
247
- const updates = { fitText: newFitText };
248
-
249
- // When enabling fit text, clear font size if it has a value
250
- if ( newFitText ) {
251
- if ( fontSize ) {
252
- updates.fontSize = undefined;
253
- }
254
- if ( style?.typography?.fontSize ) {
255
- updates.style = {
256
- ...style,
257
- typography: {
258
- ...style?.typography,
259
- fontSize: undefined,
260
- },
261
- };
262
- }
263
- }
264
-
265
- setAttributes( updates );
266
- } }
267
- help={
268
- fitText
269
- ? __( 'Text will resize to fit its container.' )
270
- : __(
271
- 'The text will resize to fit its container, resetting other font size settings.'
272
- )
273
- }
274
- />
275
- </ToolsPanelItem>
276
- </InspectorControls>
277
- );
278
- }
279
-
280
204
  /**
281
205
  * Override props applied to the block element on save.
282
206
  *
@@ -338,7 +262,7 @@ const hasFitTextSupport = ( blockNameOrType ) => {
338
262
  export default {
339
263
  useBlockProps,
340
264
  addSaveProps,
341
- attributeKeys: [ 'fitText', 'fontSize', 'style' ],
265
+ attributeKeys: [ 'fitText' ],
342
266
  hasSupport: hasFitTextSupport,
343
- edit: FitTextControl,
267
+ edit: () => null,
344
268
  };
@@ -92,11 +92,16 @@ function addSaveProps( props, blockNameOrType, attributes ) {
92
92
  */
93
93
  export function FontSizeEdit( props ) {
94
94
  const {
95
- attributes: { fontSize, style },
95
+ attributes: { fontSize, style, fitText },
96
96
  setAttributes,
97
97
  } = props;
98
+
98
99
  const [ fontSizes ] = useSettings( 'typography.fontSizes' );
99
100
 
101
+ // Hide font size UI when fitText is enabled
102
+ if ( fitText ) {
103
+ return null;
104
+ }
100
105
  const onChange = ( value, selectedItem ) => {
101
106
  // Use the selectedItem's slug if available, otherwise fall back to finding by value
102
107
  const fontSizeSlug =
@@ -211,7 +216,7 @@ function useBlockProps( { name, fontSize, style } ) {
211
216
  export default {
212
217
  useBlockProps,
213
218
  addSaveProps,
214
- attributeKeys: [ 'fontSize', 'style' ],
219
+ attributeKeys: [ 'fontSize', 'style', 'fitText' ],
215
220
  hasSupport( name ) {
216
221
  return hasBlockSupport( name, FONT_SIZE_SUPPORT_KEY );
217
222
  },
@@ -130,16 +130,7 @@ export function TypographyPanel( { clientId, name, setAttributes, settings } ) {
130
130
  );
131
131
 
132
132
  const onChange = ( newStyle ) => {
133
- const newAttributes = styleToAttributes( newStyle );
134
-
135
- // If setting a font size and fitText is currently enabled, disable it
136
- const hasFontSize =
137
- newAttributes.fontSize || newAttributes.style?.typography?.fontSize;
138
- if ( hasFontSize && fitText ) {
139
- newAttributes.fitText = undefined;
140
- }
141
-
142
- setAttributes( newAttributes );
133
+ setAttributes( styleToAttributes( newStyle ) );
143
134
  };
144
135
 
145
136
  if ( ! isEnabled ) {
@@ -159,6 +150,7 @@ export function TypographyPanel( { clientId, name, setAttributes, settings } ) {
159
150
  value={ value }
160
151
  onChange={ onChange }
161
152
  defaultControls={ defaultControls }
153
+ fitText={ fitText }
162
154
  />
163
155
  );
164
156
  }
@@ -2263,6 +2263,8 @@ export const getInserterItems = createRegistrySelector( ( select ) =>
2263
2263
  } ) );
2264
2264
  }
2265
2265
 
2266
+ // Hardcode: Collect stretch variations separately to add at the end
2267
+ const stretchVariations = [];
2266
2268
  const items = blockTypeInserterItems.reduce(
2267
2269
  ( accumulator, item ) => {
2268
2270
  const { variations = [] } = item;
@@ -2275,14 +2277,26 @@ export const getInserterItems = createRegistrySelector( ( select ) =>
2275
2277
  state,
2276
2278
  item
2277
2279
  );
2278
- accumulator.push(
2279
- ...variations.map( variationMapper )
2280
- );
2280
+ variations
2281
+ .map( variationMapper )
2282
+ .forEach( ( variation ) => {
2283
+ if (
2284
+ variation.id ===
2285
+ 'core/paragraph/stretchy-paragraph' ||
2286
+ variation.id ===
2287
+ 'core/heading/stretchy-heading'
2288
+ ) {
2289
+ stretchVariations.push( variation );
2290
+ } else {
2291
+ accumulator.push( variation );
2292
+ }
2293
+ } );
2281
2294
  }
2282
2295
  return accumulator;
2283
2296
  },
2284
2297
  []
2285
2298
  );
2299
+ items.push( ...stretchVariations );
2286
2300
 
2287
2301
  // Ensure core blocks are prioritized in the returned results,
2288
2302
  // because third party blocks can be registered earlier than
@@ -14,14 +14,31 @@ function findOptimalFontSize( textElement, applyFontSize ) {
14
14
  const alreadyHasScrollableHeight =
15
15
  textElement.scrollHeight > textElement.clientHeight;
16
16
  let minSize = 5;
17
- let maxSize = 600;
17
+ let maxSize = 2400;
18
18
  let bestSize = minSize;
19
19
 
20
+ const computedStyle = window.getComputedStyle( textElement );
21
+ const paddingLeft = parseFloat( computedStyle.paddingLeft ) || 0;
22
+ const paddingRight = parseFloat( computedStyle.paddingRight ) || 0;
23
+ const range = document.createRange();
24
+ range.selectNodeContents( textElement );
25
+
20
26
  while ( minSize <= maxSize ) {
21
27
  const midSize = Math.floor( ( minSize + maxSize ) / 2 );
22
28
  applyFontSize( midSize );
23
29
 
24
- const fitsWidth = textElement.scrollWidth <= textElement.clientWidth;
30
+ // When there is padding if the text overflows to the
31
+ // padding area, it should be considered overflowing.
32
+ // Use Range API to measure actual text content dimensions.
33
+ const rect = range.getBoundingClientRect();
34
+ const textWidth = rect.width;
35
+
36
+ // Check if text fits within the element's width and is not
37
+ // overflowing into the padding area.
38
+ const fitsWidth =
39
+ textElement.scrollWidth <= textElement.clientWidth &&
40
+ textWidth <= textElement.clientWidth - paddingLeft - paddingRight;
41
+ // Check if text fits within the element's height.
25
42
  const fitsHeight =
26
43
  alreadyHasScrollableHeight ||
27
44
  textElement.scrollHeight <= textElement.clientHeight;
@@ -33,6 +50,7 @@ function findOptimalFontSize( textElement, applyFontSize ) {
33
50
  maxSize = midSize - 1;
34
51
  }
35
52
  }
53
+ range.detach();
36
54
 
37
55
  return bestSize;
38
56
  }