@wordpress/block-editor 15.6.2 → 15.6.4
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.
- package/build/components/block-settings-menu/block-settings-dropdown.js +4 -1
- package/build/components/block-settings-menu/block-settings-dropdown.js.map +2 -2
- package/build/components/border-radius-control/utils.js +7 -3
- package/build/components/border-radius-control/utils.js.map +2 -2
- package/build/components/global-styles/border-panel.js +11 -7
- package/build/components/global-styles/border-panel.js.map +2 -2
- package/build/components/rich-text/index.js +1 -0
- package/build/components/rich-text/index.js.map +2 -2
- package/build/hooks/block-bindings.js +6 -2
- package/build/hooks/block-bindings.js.map +2 -2
- package/build/hooks/fit-text.js +45 -8
- package/build/hooks/fit-text.js.map +2 -2
- package/build/utils/fit-text-frontend.js +28 -37
- package/build/utils/fit-text-frontend.js.map +2 -2
- package/build/utils/fit-text-utils.js +7 -14
- package/build/utils/fit-text-utils.js.map +2 -2
- package/build-module/components/block-settings-menu/block-settings-dropdown.js +4 -1
- package/build-module/components/block-settings-menu/block-settings-dropdown.js.map +2 -2
- package/build-module/components/border-radius-control/utils.js +7 -3
- package/build-module/components/border-radius-control/utils.js.map +2 -2
- package/build-module/components/global-styles/border-panel.js +11 -7
- package/build-module/components/global-styles/border-panel.js.map +2 -2
- package/build-module/components/rich-text/index.js +1 -0
- package/build-module/components/rich-text/index.js.map +2 -2
- package/build-module/hooks/block-bindings.js +6 -2
- package/build-module/hooks/block-bindings.js.map +2 -2
- package/build-module/hooks/fit-text.js +45 -8
- package/build-module/hooks/fit-text.js.map +2 -2
- package/build-module/utils/fit-text-frontend.js +28 -37
- package/build-module/utils/fit-text-frontend.js.map +2 -2
- package/build-module/utils/fit-text-utils.js +7 -14
- package/build-module/utils/fit-text-utils.js.map +2 -2
- package/package.json +7 -6
- package/src/components/block-settings-menu/block-settings-dropdown.js +4 -1
- package/src/components/border-radius-control/test/utils.js +90 -0
- package/src/components/border-radius-control/utils.js +7 -3
- package/src/components/global-styles/border-panel.js +11 -7
- package/src/components/rich-text/index.js +1 -0
- package/src/hooks/block-bindings.js +6 -2
- package/src/hooks/fit-text.js +63 -12
- package/src/utils/fit-text-frontend.js +41 -73
- package/src/utils/fit-text-utils.js +12 -29
- package/tsconfig.json +1 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/utils/fit-text-frontend.js"],
|
|
4
|
-
"sourcesContent": ["/**\n * Frontend fit text functionality.\n * Automatically detects and initializes fit text on blocks with the has-fit-text class.\n */\n\n/**\n *
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\n * Frontend fit text functionality.\n * Automatically detects and initializes fit text on blocks with the has-fit-text class.\n * Supports both initial page load and Interactivity API client-side navigation.\n */\n\n/**\n * WordPress dependencies\n */\nimport { store, getElement, getContext } from '@wordpress/interactivity';\n\n/**\n * Internal dependencies\n */\nimport { optimizeFitText } from './fit-text-utils';\n\n// Initialize via Interactivity API for client-side navigation\nstore( 'core/fit-text', {\n\tcallbacks: {\n\t\tinit() {\n\t\t\tconst context = getContext();\n\t\t\tconst { ref } = getElement();\n\n\t\t\tconst applyFontSize = ( fontSize ) => {\n\t\t\t\tif ( fontSize === 0 ) {\n\t\t\t\t\tref.style.fontSize = '';\n\t\t\t\t} else {\n\t\t\t\t\tref.style.fontSize = `${ fontSize }px`;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t// Initial fit text optimization.\n\t\t\tcontext.fontSize = optimizeFitText( ref, applyFontSize );\n\n\t\t\t// Starts ResizeObserver to handle dynamic resizing.\n\t\t\tif ( window.ResizeObserver && ref.parentElement ) {\n\t\t\t\tconst resizeObserver = new window.ResizeObserver( () => {\n\t\t\t\t\tcontext.fontSize = optimizeFitText( ref, applyFontSize );\n\t\t\t\t} );\n\t\t\t\tresizeObserver.observe( ref.parentElement );\n\t\t\t\tresizeObserver.observe( ref );\n\n\t\t\t\t// Return cleanup function to be called when element is removed.\n\t\t\t\treturn () => {\n\t\t\t\t\tif ( resizeObserver ) {\n\t\t\t\t\t\tresizeObserver.disconnect();\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t},\n\t},\n} );\n"],
|
|
5
|
+
"mappings": "AASA,SAAS,OAAO,YAAY,kBAAkB;AAK9C,SAAS,uBAAuB;AAGhC,MAAO,iBAAiB;AAAA,EACvB,WAAW;AAAA,IACV,OAAO;AACN,YAAM,UAAU,WAAW;AAC3B,YAAM,EAAE,IAAI,IAAI,WAAW;AAE3B,YAAM,gBAAgB,CAAE,aAAc;AACrC,YAAK,aAAa,GAAI;AACrB,cAAI,MAAM,WAAW;AAAA,QACtB,OAAO;AACN,cAAI,MAAM,WAAW,GAAI,QAAS;AAAA,QACnC;AAAA,MACD;AAGA,cAAQ,WAAW,gBAAiB,KAAK,aAAc;AAGvD,UAAK,OAAO,kBAAkB,IAAI,eAAgB;AACjD,cAAM,iBAAiB,IAAI,OAAO,eAAgB,MAAM;AACvD,kBAAQ,WAAW,gBAAiB,KAAK,aAAc;AAAA,QACxD,CAAE;AACF,uBAAe,QAAS,IAAI,aAAc;AAC1C,uBAAe,QAAS,GAAI;AAG5B,eAAO,MAAM;AACZ,cAAK,gBAAiB;AACrB,2BAAe,WAAW;AAAA,UAC3B;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACD,CAAE;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
function
|
|
2
|
-
return `${elementSelector} { font-size: ${fontSize}px !important; }`;
|
|
3
|
-
}
|
|
4
|
-
function findOptimalFontSize(textElement, elementSelector, applyStylesFn) {
|
|
1
|
+
function findOptimalFontSize(textElement, applyFontSize) {
|
|
5
2
|
const alreadyHasScrollableHeight = textElement.scrollHeight > textElement.clientHeight;
|
|
6
3
|
let minSize = 5;
|
|
7
4
|
let maxSize = 600;
|
|
8
5
|
let bestSize = minSize;
|
|
9
6
|
while (minSize <= maxSize) {
|
|
10
7
|
const midSize = Math.floor((minSize + maxSize) / 2);
|
|
11
|
-
|
|
8
|
+
applyFontSize(midSize);
|
|
12
9
|
const fitsWidth = textElement.scrollWidth <= textElement.clientWidth;
|
|
13
10
|
const fitsHeight = alreadyHasScrollableHeight || textElement.scrollHeight <= textElement.clientHeight;
|
|
14
11
|
if (fitsWidth && fitsHeight) {
|
|
@@ -20,18 +17,14 @@ function findOptimalFontSize(textElement, elementSelector, applyStylesFn) {
|
|
|
20
17
|
}
|
|
21
18
|
return bestSize;
|
|
22
19
|
}
|
|
23
|
-
function optimizeFitText(textElement,
|
|
20
|
+
function optimizeFitText(textElement, applyFontSize) {
|
|
24
21
|
if (!textElement) {
|
|
25
22
|
return;
|
|
26
23
|
}
|
|
27
|
-
|
|
28
|
-
const optimalSize = findOptimalFontSize(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
applyStylesFn
|
|
32
|
-
);
|
|
33
|
-
const cssRule = generateCSSRule(elementSelector, optimalSize);
|
|
34
|
-
applyStylesFn(cssRule);
|
|
24
|
+
applyFontSize(0);
|
|
25
|
+
const optimalSize = findOptimalFontSize(textElement, applyFontSize);
|
|
26
|
+
applyFontSize(optimalSize);
|
|
27
|
+
return optimalSize;
|
|
35
28
|
}
|
|
36
29
|
export {
|
|
37
30
|
optimizeFitText
|
|
@@ -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 *
|
|
5
|
-
"mappings": "AAYA,SAAS,
|
|
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;",
|
|
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.
|
|
3
|
+
"version": "15.6.4",
|
|
4
4
|
"description": "Generic block editor.",
|
|
5
5
|
"author": "The WordPress Contributors",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
"@wordpress/blob": "^4.33.1",
|
|
63
63
|
"@wordpress/block-serialization-default-parser": "^5.33.1",
|
|
64
64
|
"@wordpress/blocks": "^15.6.1",
|
|
65
|
-
"@wordpress/commands": "^1.33.
|
|
66
|
-
"@wordpress/components": "^30.6.
|
|
65
|
+
"@wordpress/commands": "^1.33.2",
|
|
66
|
+
"@wordpress/components": "^30.6.2",
|
|
67
67
|
"@wordpress/compose": "^7.33.1",
|
|
68
68
|
"@wordpress/data": "^10.33.1",
|
|
69
69
|
"@wordpress/date": "^5.33.1",
|
|
@@ -75,17 +75,18 @@
|
|
|
75
75
|
"@wordpress/html-entities": "^4.33.1",
|
|
76
76
|
"@wordpress/i18n": "^6.6.1",
|
|
77
77
|
"@wordpress/icons": "^11.0.1",
|
|
78
|
+
"@wordpress/interactivity": "^6.33.1",
|
|
78
79
|
"@wordpress/is-shallow-equal": "^5.33.1",
|
|
79
80
|
"@wordpress/keyboard-shortcuts": "^5.33.1",
|
|
80
81
|
"@wordpress/keycodes": "^4.33.1",
|
|
81
82
|
"@wordpress/notices": "^5.33.1",
|
|
82
|
-
"@wordpress/preferences": "^4.33.
|
|
83
|
+
"@wordpress/preferences": "^4.33.2",
|
|
83
84
|
"@wordpress/priority-queue": "^3.33.1",
|
|
84
85
|
"@wordpress/private-apis": "^1.33.1",
|
|
85
86
|
"@wordpress/rich-text": "^7.33.1",
|
|
86
87
|
"@wordpress/style-engine": "^2.33.1",
|
|
87
88
|
"@wordpress/token-list": "^3.33.1",
|
|
88
|
-
"@wordpress/upload-media": "^0.18.
|
|
89
|
+
"@wordpress/upload-media": "^0.18.2",
|
|
89
90
|
"@wordpress/url": "^4.33.1",
|
|
90
91
|
"@wordpress/warning": "^3.33.1",
|
|
91
92
|
"@wordpress/wordcount": "^4.33.1",
|
|
@@ -111,5 +112,5 @@
|
|
|
111
112
|
"publishConfig": {
|
|
112
113
|
"access": "public"
|
|
113
114
|
},
|
|
114
|
-
"gitHead": "
|
|
115
|
+
"gitHead": "2e2a11a11f0c5c9cb2bba2dd40b8046d2a10dc9d"
|
|
115
116
|
}
|
|
@@ -107,6 +107,96 @@ describe( 'getAllValue', () => {
|
|
|
107
107
|
expect( getAllValue( undefined ) ).toBe( undefined );
|
|
108
108
|
} );
|
|
109
109
|
} );
|
|
110
|
+
|
|
111
|
+
describe( 'when provided complex CSS values (clamp, min, max, calc)', () => {
|
|
112
|
+
it( 'should preserve clamp values when all corners have the same clamp value', () => {
|
|
113
|
+
const clampValue = 'clamp(1rem, 2vw, 3rem)';
|
|
114
|
+
const values = {
|
|
115
|
+
bottomLeft: clampValue,
|
|
116
|
+
bottomRight: clampValue,
|
|
117
|
+
topLeft: clampValue,
|
|
118
|
+
topRight: clampValue,
|
|
119
|
+
};
|
|
120
|
+
expect( getAllValue( values ) ).toBe( clampValue );
|
|
121
|
+
} );
|
|
122
|
+
|
|
123
|
+
it( 'should preserve min() values when all corners have the same min value', () => {
|
|
124
|
+
const minValue = 'min(10px, 5vw)';
|
|
125
|
+
const values = {
|
|
126
|
+
bottomLeft: minValue,
|
|
127
|
+
bottomRight: minValue,
|
|
128
|
+
topLeft: minValue,
|
|
129
|
+
topRight: minValue,
|
|
130
|
+
};
|
|
131
|
+
expect( getAllValue( values ) ).toBe( minValue );
|
|
132
|
+
} );
|
|
133
|
+
|
|
134
|
+
it( 'should preserve max() values when all corners have the same max value', () => {
|
|
135
|
+
const maxValue = 'max(20px, 10vw)';
|
|
136
|
+
const values = {
|
|
137
|
+
bottomLeft: maxValue,
|
|
138
|
+
bottomRight: maxValue,
|
|
139
|
+
topLeft: maxValue,
|
|
140
|
+
topRight: maxValue,
|
|
141
|
+
};
|
|
142
|
+
expect( getAllValue( values ) ).toBe( maxValue );
|
|
143
|
+
} );
|
|
144
|
+
|
|
145
|
+
it( 'should preserve calc() values when all corners have the same calc value', () => {
|
|
146
|
+
const calcValue = 'calc(100% - 20px)';
|
|
147
|
+
const values = {
|
|
148
|
+
bottomLeft: calcValue,
|
|
149
|
+
bottomRight: calcValue,
|
|
150
|
+
topLeft: calcValue,
|
|
151
|
+
topRight: calcValue,
|
|
152
|
+
};
|
|
153
|
+
expect( getAllValue( values ) ).toBe( calcValue );
|
|
154
|
+
} );
|
|
155
|
+
|
|
156
|
+
it( 'should return undefined when complex CSS values are mixed', () => {
|
|
157
|
+
const values = {
|
|
158
|
+
bottomLeft: 'clamp(1rem, 2vw, 3rem)',
|
|
159
|
+
bottomRight: 'clamp(1rem, 2vw, 3rem)',
|
|
160
|
+
topLeft: 'min(10px, 5vw)',
|
|
161
|
+
topRight: 'clamp(1rem, 2vw, 3rem)',
|
|
162
|
+
};
|
|
163
|
+
expect( getAllValue( values ) ).toBe( undefined );
|
|
164
|
+
} );
|
|
165
|
+
|
|
166
|
+
it( 'should return undefined when complex CSS values are mixed with simple values', () => {
|
|
167
|
+
const values = {
|
|
168
|
+
bottomLeft: 'clamp(1rem, 2vw, 3rem)',
|
|
169
|
+
bottomRight: 'clamp(1rem, 2vw, 3rem)',
|
|
170
|
+
topLeft: '2px',
|
|
171
|
+
topRight: 'clamp(1rem, 2vw, 3rem)',
|
|
172
|
+
};
|
|
173
|
+
expect( getAllValue( values ) ).toBe( undefined );
|
|
174
|
+
} );
|
|
175
|
+
|
|
176
|
+
it( 'should preserve string values that cannot be parsed at all (no numeric prefix)', () => {
|
|
177
|
+
// Values with no numeric prefix cannot be parsed, so they should be preserved
|
|
178
|
+
const unparseableValue = 'apples';
|
|
179
|
+
const values = {
|
|
180
|
+
bottomLeft: unparseableValue,
|
|
181
|
+
bottomRight: unparseableValue,
|
|
182
|
+
topLeft: unparseableValue,
|
|
183
|
+
topRight: unparseableValue,
|
|
184
|
+
};
|
|
185
|
+
expect( getAllValue( values ) ).toBe( unparseableValue );
|
|
186
|
+
} );
|
|
187
|
+
|
|
188
|
+
it( 'should parse numeric prefix from partially parseable values', () => {
|
|
189
|
+
// Values with numeric prefix get parsed, so "32apples" becomes "32"
|
|
190
|
+
const partiallyParseableValue = '32apples';
|
|
191
|
+
const values = {
|
|
192
|
+
bottomLeft: partiallyParseableValue,
|
|
193
|
+
bottomRight: partiallyParseableValue,
|
|
194
|
+
topLeft: partiallyParseableValue,
|
|
195
|
+
topRight: partiallyParseableValue,
|
|
196
|
+
};
|
|
197
|
+
expect( getAllValue( values ) ).toBe( '32' );
|
|
198
|
+
} );
|
|
199
|
+
} );
|
|
110
200
|
} );
|
|
111
201
|
|
|
112
202
|
describe( 'hasMixedValues', () => {
|
|
@@ -60,9 +60,13 @@ export function getAllValue( values = {} ) {
|
|
|
60
60
|
return values;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
const parsedQuantitiesAndUnits = Object.values( values ).map( ( value ) =>
|
|
64
|
-
parseQuantityAndUnitFromRawValue( value )
|
|
65
|
-
|
|
63
|
+
const parsedQuantitiesAndUnits = Object.values( values ).map( ( value ) => {
|
|
64
|
+
const newValue = parseQuantityAndUnitFromRawValue( value );
|
|
65
|
+
if ( typeof value === 'string' && newValue[ 0 ] === undefined ) {
|
|
66
|
+
return [ value, '' ];
|
|
67
|
+
}
|
|
68
|
+
return newValue;
|
|
69
|
+
} );
|
|
66
70
|
|
|
67
71
|
const allValues = parsedQuantitiesAndUnits.map(
|
|
68
72
|
( value ) => value[ 0 ] ?? ''
|
|
@@ -146,17 +146,21 @@ export default function BorderPanel( {
|
|
|
146
146
|
// Border radius.
|
|
147
147
|
const showBorderRadius = useHasBorderRadiusControl( settings );
|
|
148
148
|
const borderRadiusValues = useMemo( () => {
|
|
149
|
-
if ( typeof border?.radius !== 'object' ) {
|
|
150
|
-
return border?.radius;
|
|
149
|
+
if ( typeof inheritedValue?.border?.radius !== 'object' ) {
|
|
150
|
+
return decodeValue( inheritedValue?.border?.radius );
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
return {
|
|
154
|
-
topLeft: border?.radius?.topLeft,
|
|
155
|
-
topRight: border?.radius?.topRight,
|
|
156
|
-
bottomLeft:
|
|
157
|
-
|
|
154
|
+
topLeft: decodeValue( inheritedValue?.border?.radius?.topLeft ),
|
|
155
|
+
topRight: decodeValue( inheritedValue?.border?.radius?.topRight ),
|
|
156
|
+
bottomLeft: decodeValue(
|
|
157
|
+
inheritedValue?.border?.radius?.bottomLeft
|
|
158
|
+
),
|
|
159
|
+
bottomRight: decodeValue(
|
|
160
|
+
inheritedValue?.border?.radius?.bottomRight
|
|
161
|
+
),
|
|
158
162
|
};
|
|
159
|
-
}, [ border?.radius ] );
|
|
163
|
+
}, [ inheritedValue?.border?.radius, decodeValue ] );
|
|
160
164
|
const setBorderRadius = ( newBorderRadius ) =>
|
|
161
165
|
setBorder( { ...border, radius: newBorderRadius } );
|
|
162
166
|
const hasBorderRadius = () => {
|
|
@@ -449,7 +449,11 @@ export const BlockBindingsPanel = ( { name: blockName, metadata } ) => {
|
|
|
449
449
|
export default {
|
|
450
450
|
edit: BlockBindingsPanel,
|
|
451
451
|
attributeKeys: [ 'metadata' ],
|
|
452
|
-
hasSupport() {
|
|
453
|
-
return
|
|
452
|
+
hasSupport( name ) {
|
|
453
|
+
return ! [
|
|
454
|
+
'core/post-date',
|
|
455
|
+
'core/navigation-link',
|
|
456
|
+
'core/navigation-submenu',
|
|
457
|
+
].includes( name );
|
|
454
458
|
},
|
|
455
459
|
};
|
package/src/hooks/fit-text.js
CHANGED
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
__experimentalToolsPanelItem as ToolsPanelItem,
|
|
12
12
|
} from '@wordpress/components';
|
|
13
13
|
|
|
14
|
+
const EMPTY_OBJECT = {};
|
|
15
|
+
|
|
14
16
|
/**
|
|
15
17
|
* Internal dependencies
|
|
16
18
|
*/
|
|
@@ -62,14 +64,19 @@ function useFitText( { fitText, name, clientId } ) {
|
|
|
62
64
|
const hasFitTextSupport = hasBlockSupport( name, FIT_TEXT_SUPPORT_KEY );
|
|
63
65
|
const blockElement = useBlockElement( clientId );
|
|
64
66
|
|
|
65
|
-
// Monitor block attribute changes
|
|
66
|
-
// Any attribute may change the available space.
|
|
67
|
-
const blockAttributes = useSelect(
|
|
67
|
+
// Monitor block attribute changes, and parent changes.
|
|
68
|
+
// Any attribute or parent change may change the available space.
|
|
69
|
+
const { blockAttributes, parentId } = useSelect(
|
|
68
70
|
( select ) => {
|
|
69
71
|
if ( ! clientId || ! hasFitTextSupport || ! fitText ) {
|
|
70
|
-
return;
|
|
72
|
+
return EMPTY_OBJECT;
|
|
71
73
|
}
|
|
72
|
-
return
|
|
74
|
+
return {
|
|
75
|
+
blockAttributes:
|
|
76
|
+
select( blockEditorStore ).getBlockAttributes( clientId ),
|
|
77
|
+
parentId:
|
|
78
|
+
select( blockEditorStore ).getBlockRootClientId( clientId ),
|
|
79
|
+
};
|
|
73
80
|
},
|
|
74
81
|
[ clientId, hasFitTextSupport, fitText ]
|
|
75
82
|
);
|
|
@@ -90,11 +97,15 @@ function useFitText( { fitText, name, clientId } ) {
|
|
|
90
97
|
|
|
91
98
|
const blockSelector = `#block-${ clientId }`;
|
|
92
99
|
|
|
93
|
-
const
|
|
94
|
-
|
|
100
|
+
const applyFontSize = ( fontSize ) => {
|
|
101
|
+
if ( fontSize === 0 ) {
|
|
102
|
+
styleElement.textContent = '';
|
|
103
|
+
} else {
|
|
104
|
+
styleElement.textContent = `${ blockSelector } { font-size: ${ fontSize }px !important; }`;
|
|
105
|
+
}
|
|
95
106
|
};
|
|
96
107
|
|
|
97
|
-
optimizeFitText( blockElement,
|
|
108
|
+
optimizeFitText( blockElement, applyFontSize );
|
|
98
109
|
}, [ blockElement, clientId, hasFitTextSupport, fitText ] );
|
|
99
110
|
|
|
100
111
|
useEffect( () => {
|
|
@@ -107,21 +118,54 @@ function useFitText( { fitText, name, clientId } ) {
|
|
|
107
118
|
return;
|
|
108
119
|
}
|
|
109
120
|
|
|
110
|
-
// Apply initially
|
|
111
|
-
applyFitText();
|
|
112
|
-
|
|
113
121
|
// Store current element value for cleanup
|
|
114
122
|
const currentElement = blockElement;
|
|
123
|
+
const previousVisibility = currentElement.style.visibility;
|
|
124
|
+
|
|
125
|
+
// Store IDs for cleanup
|
|
126
|
+
let hideFrameId = null;
|
|
127
|
+
let calculateFrameId = null;
|
|
128
|
+
let showTimeoutId = null;
|
|
129
|
+
|
|
130
|
+
// We are hiding the element doing the calculation of fit text
|
|
131
|
+
// and then showing it again to avoid the user noticing a flash of potentially
|
|
132
|
+
// big fitText while the binary search is happening.
|
|
133
|
+
hideFrameId = window.requestAnimationFrame( () => {
|
|
134
|
+
currentElement.style.visibility = 'hidden';
|
|
135
|
+
// Wait for browser to render the hidden state
|
|
136
|
+
calculateFrameId = window.requestAnimationFrame( () => {
|
|
137
|
+
applyFitText();
|
|
138
|
+
|
|
139
|
+
// Using a timeout instead of requestAnimationFrame, because
|
|
140
|
+
// with requestAnimationFrame a flash of very high size
|
|
141
|
+
// can still occur although rare.
|
|
142
|
+
showTimeoutId = setTimeout( () => {
|
|
143
|
+
currentElement.style.visibility = previousVisibility;
|
|
144
|
+
}, 10 );
|
|
145
|
+
} );
|
|
146
|
+
} );
|
|
115
147
|
|
|
116
148
|
// Watch for size changes
|
|
117
149
|
let resizeObserver;
|
|
118
150
|
if ( window.ResizeObserver && currentElement.parentElement ) {
|
|
119
151
|
resizeObserver = new window.ResizeObserver( applyFitText );
|
|
120
152
|
resizeObserver.observe( currentElement.parentElement );
|
|
153
|
+
resizeObserver.observe( currentElement );
|
|
121
154
|
}
|
|
122
155
|
|
|
123
156
|
// Cleanup function
|
|
124
157
|
return () => {
|
|
158
|
+
// Cancel pending async operations
|
|
159
|
+
if ( hideFrameId !== null ) {
|
|
160
|
+
window.cancelAnimationFrame( hideFrameId );
|
|
161
|
+
}
|
|
162
|
+
if ( calculateFrameId !== null ) {
|
|
163
|
+
window.cancelAnimationFrame( calculateFrameId );
|
|
164
|
+
}
|
|
165
|
+
if ( showTimeoutId !== null ) {
|
|
166
|
+
clearTimeout( showTimeoutId );
|
|
167
|
+
}
|
|
168
|
+
|
|
125
169
|
if ( resizeObserver ) {
|
|
126
170
|
resizeObserver.disconnect();
|
|
127
171
|
}
|
|
@@ -133,7 +177,14 @@ function useFitText( { fitText, name, clientId } ) {
|
|
|
133
177
|
styleElement.remove();
|
|
134
178
|
}
|
|
135
179
|
};
|
|
136
|
-
}, [
|
|
180
|
+
}, [
|
|
181
|
+
fitText,
|
|
182
|
+
clientId,
|
|
183
|
+
parentId,
|
|
184
|
+
applyFitText,
|
|
185
|
+
blockElement,
|
|
186
|
+
hasFitTextSupport,
|
|
187
|
+
] );
|
|
137
188
|
|
|
138
189
|
// Trigger fit text recalculation when content changes
|
|
139
190
|
useEffect( () => {
|
|
@@ -1,84 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Frontend fit text functionality.
|
|
3
3
|
* Automatically detects and initializes fit text on blocks with the has-fit-text class.
|
|
4
|
+
* Supports both initial page load and Interactivity API client-side navigation.
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
-
*/
|
|
9
|
-
import { optimizeFitText } from './fit-text-utils';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Counter for generating unique element IDs.
|
|
13
|
-
*/
|
|
14
|
-
let idCounter = 0;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Get or create a unique style element for a fit text element.
|
|
18
|
-
*
|
|
19
|
-
* @param {string} elementId Unique identifier for the element.
|
|
20
|
-
* @return {HTMLElement} Style element.
|
|
21
|
-
*/
|
|
22
|
-
function getOrCreateStyleElement( elementId ) {
|
|
23
|
-
const styleId = `fit-text-${ elementId }`;
|
|
24
|
-
let styleElement = document.getElementById( styleId );
|
|
25
|
-
if ( ! styleElement ) {
|
|
26
|
-
styleElement = document.createElement( 'style' );
|
|
27
|
-
styleElement.id = styleId;
|
|
28
|
-
document.head.appendChild( styleElement );
|
|
29
|
-
}
|
|
30
|
-
return styleElement;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Generate a unique identifier for a fit text element.
|
|
35
|
-
*
|
|
36
|
-
* @param {HTMLElement} element The element to identify.
|
|
37
|
-
* @return {string} Unique identifier.
|
|
8
|
+
* WordPress dependencies
|
|
38
9
|
*/
|
|
39
|
-
|
|
40
|
-
if ( ! element.dataset.fitTextId ) {
|
|
41
|
-
element.dataset.fitTextId = `fit-text-${ ++idCounter }`;
|
|
42
|
-
}
|
|
43
|
-
return element.dataset.fitTextId;
|
|
44
|
-
}
|
|
10
|
+
import { store, getElement, getContext } from '@wordpress/interactivity';
|
|
45
11
|
|
|
46
12
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* @param {HTMLElement} element Element with fit text enabled.
|
|
50
|
-
*/
|
|
51
|
-
function initializeFitText( element ) {
|
|
52
|
-
const elementId = getElementIdentifier( element );
|
|
53
|
-
|
|
54
|
-
const applyFitText = () => {
|
|
55
|
-
const styleElement = getOrCreateStyleElement( elementId );
|
|
56
|
-
const elementSelector = `[data-fit-text-id=\"${ elementId }\"]`;
|
|
57
|
-
|
|
58
|
-
// Style management callback
|
|
59
|
-
const applyStylesFn = ( css ) => {
|
|
60
|
-
styleElement.textContent = css;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
optimizeFitText( element, elementSelector, applyStylesFn );
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
// Initial sizing
|
|
67
|
-
applyFitText();
|
|
68
|
-
|
|
69
|
-
// Watch for parent container resize
|
|
70
|
-
if ( window.ResizeObserver && element.parentElement ) {
|
|
71
|
-
const resizeObserver = new window.ResizeObserver( applyFitText );
|
|
72
|
-
resizeObserver.observe( element.parentElement );
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Initialize fit text on all elements with the has-fit-text class.
|
|
13
|
+
* Internal dependencies
|
|
78
14
|
*/
|
|
79
|
-
|
|
80
|
-
const elements = document.querySelectorAll( '.has-fit-text' );
|
|
81
|
-
elements.forEach( initializeFitText );
|
|
82
|
-
}
|
|
15
|
+
import { optimizeFitText } from './fit-text-utils';
|
|
83
16
|
|
|
84
|
-
|
|
17
|
+
// Initialize via Interactivity API for client-side navigation
|
|
18
|
+
store( 'core/fit-text', {
|
|
19
|
+
callbacks: {
|
|
20
|
+
init() {
|
|
21
|
+
const context = getContext();
|
|
22
|
+
const { ref } = getElement();
|
|
23
|
+
|
|
24
|
+
const applyFontSize = ( fontSize ) => {
|
|
25
|
+
if ( fontSize === 0 ) {
|
|
26
|
+
ref.style.fontSize = '';
|
|
27
|
+
} else {
|
|
28
|
+
ref.style.fontSize = `${ fontSize }px`;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Initial fit text optimization.
|
|
33
|
+
context.fontSize = optimizeFitText( ref, applyFontSize );
|
|
34
|
+
|
|
35
|
+
// Starts ResizeObserver to handle dynamic resizing.
|
|
36
|
+
if ( window.ResizeObserver && ref.parentElement ) {
|
|
37
|
+
const resizeObserver = new window.ResizeObserver( () => {
|
|
38
|
+
context.fontSize = optimizeFitText( ref, applyFontSize );
|
|
39
|
+
} );
|
|
40
|
+
resizeObserver.observe( ref.parentElement );
|
|
41
|
+
resizeObserver.observe( ref );
|
|
42
|
+
|
|
43
|
+
// Return cleanup function to be called when element is removed.
|
|
44
|
+
return () => {
|
|
45
|
+
if ( resizeObserver ) {
|
|
46
|
+
resizeObserver.disconnect();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
} );
|
|
@@ -3,26 +3,14 @@
|
|
|
3
3
|
* Uses callback-based approach for maximum code reuse between editor and frontend.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Generate CSS rule for single text element.
|
|
8
|
-
*
|
|
9
|
-
* @param {string} elementSelector CSS selector for the text element
|
|
10
|
-
* @param {number} fontSize Font size in pixels
|
|
11
|
-
* @return {string} CSS rule string
|
|
12
|
-
*/
|
|
13
|
-
function generateCSSRule( elementSelector, fontSize ) {
|
|
14
|
-
return `${ elementSelector } { font-size: ${ fontSize }px !important; }`;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
6
|
/**
|
|
18
7
|
* Find optimal font size using simple binary search between 5-600px.
|
|
19
8
|
*
|
|
20
|
-
* @param {HTMLElement} textElement
|
|
21
|
-
* @param {
|
|
22
|
-
* @param {Function} applyStylesFn Function to apply test styles
|
|
9
|
+
* @param {HTMLElement} textElement The text element
|
|
10
|
+
* @param {Function} applyFontSize Function that receives font size in pixels
|
|
23
11
|
* @return {number} Optimal font size
|
|
24
12
|
*/
|
|
25
|
-
function findOptimalFontSize( textElement,
|
|
13
|
+
function findOptimalFontSize( textElement, applyFontSize ) {
|
|
26
14
|
const alreadyHasScrollableHeight =
|
|
27
15
|
textElement.scrollHeight > textElement.clientHeight;
|
|
28
16
|
let minSize = 5;
|
|
@@ -31,7 +19,7 @@ function findOptimalFontSize( textElement, elementSelector, applyStylesFn ) {
|
|
|
31
19
|
|
|
32
20
|
while ( minSize <= maxSize ) {
|
|
33
21
|
const midSize = Math.floor( ( minSize + maxSize ) / 2 );
|
|
34
|
-
|
|
22
|
+
applyFontSize( midSize );
|
|
35
23
|
|
|
36
24
|
const fitsWidth = textElement.scrollWidth <= textElement.clientWidth;
|
|
37
25
|
const fitsHeight =
|
|
@@ -51,25 +39,20 @@ function findOptimalFontSize( textElement, elementSelector, applyStylesFn ) {
|
|
|
51
39
|
|
|
52
40
|
/**
|
|
53
41
|
* Complete fit text optimization for a single text element.
|
|
54
|
-
* Handles the full flow using callbacks for
|
|
42
|
+
* Handles the full flow using callbacks for font size application.
|
|
55
43
|
*
|
|
56
|
-
* @param {HTMLElement} textElement
|
|
57
|
-
* @param {
|
|
58
|
-
* @param {Function} applyStylesFn Function to apply CSS styles (pass empty string to clear)
|
|
44
|
+
* @param {HTMLElement} textElement The text element (paragraph, heading, etc.)
|
|
45
|
+
* @param {Function} applyFontSize Function that receives font size in pixels (0 to clear, >0 to apply)
|
|
59
46
|
*/
|
|
60
|
-
export function optimizeFitText( textElement,
|
|
47
|
+
export function optimizeFitText( textElement, applyFontSize ) {
|
|
61
48
|
if ( ! textElement ) {
|
|
62
49
|
return;
|
|
63
50
|
}
|
|
64
51
|
|
|
65
|
-
|
|
52
|
+
applyFontSize( 0 );
|
|
66
53
|
|
|
67
|
-
const optimalSize = findOptimalFontSize(
|
|
68
|
-
textElement,
|
|
69
|
-
elementSelector,
|
|
70
|
-
applyStylesFn
|
|
71
|
-
);
|
|
54
|
+
const optimalSize = findOptimalFontSize( textElement, applyFontSize );
|
|
72
55
|
|
|
73
|
-
|
|
74
|
-
|
|
56
|
+
applyFontSize( optimalSize );
|
|
57
|
+
return optimalSize;
|
|
75
58
|
}
|