forked from WordPress/gutenberg
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutils.js
More file actions
293 lines (255 loc) · 7.81 KB
/
utils.js
File metadata and controls
293 lines (255 loc) · 7.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
/**
* WordPress dependencies
*/
import { __experimentalParseQuantityAndUnitFromRawValue as parseQuantityAndUnitFromRawValue } from '@wordpress/components';
/**
* Gets the (non-undefined) item with the highest occurrence within an array
* Based in part on: https://stackoverflow.com/a/20762713
*
* Undefined values are always sorted to the end by `sort`, so this function
* returns the first element, to always prioritize real values over undefined
* values.
*
* See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#description
*
* @param {Array<any>} inputArray Array of items to check.
* @return {any} The item with the most occurrences.
*/
export function mode( inputArray ) {
const arr = [ ...inputArray ];
return arr
.sort(
( a, b ) =>
inputArray.filter( ( v ) => v === b ).length -
inputArray.filter( ( v ) => v === a ).length
)
.shift();
}
/**
* Returns the most common CSS unit from the current CSS unit selections.
*
* - If a single flat border radius is set, its unit will be used
* - If individual corner selections, the most common of those will be used
* - Failing any unit selections a default of 'px' is returned.
*
* @param {Object} selectedUnits Unit selections for flat radius & each corner.
* @return {string} Most common CSS unit from current selections. Default: `px`.
*/
export function getAllUnit( selectedUnits = {} ) {
const { flat, ...cornerUnits } = selectedUnits;
return (
flat || mode( Object.values( cornerUnits ).filter( Boolean ) ) || 'px'
);
}
/**
* Gets the 'all' input value and unit from values data.
*
* @param {Object|string} values Radius values.
* @return {string} A value + unit for the 'all' input.
*/
export function getAllValue( values = {} ) {
/**
* Border radius support was originally a single pixel value.
*
* To maintain backwards compatibility treat this case as the all value.
*/
if ( typeof values === 'string' ) {
return values;
}
const parsedQuantitiesAndUnits = Object.values( values ).map( ( value ) =>
parseQuantityAndUnitFromRawValue( value )
);
const allValues = parsedQuantitiesAndUnits.map(
( value ) => value[ 0 ] ?? ''
);
const allUnits = parsedQuantitiesAndUnits.map( ( value ) => value[ 1 ] );
const value = allValues.every( ( v ) => v === allValues[ 0 ] )
? allValues[ 0 ]
: '';
const unit = mode( allUnits );
const allValue =
value === 0 || value ? `${ value }${ unit || '' }` : undefined;
return allValue;
}
/**
* Checks to determine if values are mixed.
*
* @param {Object} values Radius values.
* @return {boolean} Whether values are mixed.
*/
export function hasMixedValues( values = {} ) {
if ( typeof values === 'string' ) {
return false;
}
if ( ! values || typeof values !== 'object' ) {
return false;
}
const cornerValues = Object.values( values );
if ( cornerValues.length === 0 ) {
return false;
}
const firstValue = cornerValues[ 0 ];
// Check if all values are exactly the same (including undefined)
const allSame = cornerValues.every( ( value ) => value === firstValue );
return ! allSame;
}
/**
* Checks to determine if values are defined.
*
* @param {Object} values Radius values.
* @return {boolean} Whether values are mixed.
*/
export function hasDefinedValues( values ) {
if ( ! values ) {
return false;
}
// A string value represents a shorthand value.
if ( typeof values === 'string' ) {
return true;
}
// An object represents longhand border radius values, if any are set
// flag values as being defined.
const filteredValues = Object.values( values ).filter( ( value ) => {
return !! value || value === 0;
} );
return !! filteredValues.length;
}
/**
* Checks is given value is a radius preset.
*
* @param {string} value Value to check
*
* @return {boolean} Return true if value is string in format var:preset|border-radius|.
*/
export function isValuePreset( value ) {
if ( ! value?.includes ) {
return false;
}
return value === '0' || value.includes( 'var:preset|border-radius|' );
}
/**
* Returns the slug section of the given preset string.
*
* @param {string} value Value to extract slug from.
*
* @return {string|undefined} The int value of the slug from given preset.
*/
export function getPresetSlug( value ) {
if ( ! value ) {
return;
}
if ( value === '0' || value === 'default' ) {
return value;
}
const slug = value.match( /var:preset\|border-radius\|(.+)/ );
return slug ? slug[ 1 ] : undefined;
}
/**
* Converts radius preset value into a Range component value .
*
* @param {string} presetValue Value to convert to Range value.
* @param {Array} presets Array of current radius preset value objects.
*
* @return {number} The int value for use in Range control.
*/
export function getSliderValueFromPreset( presetValue, presets ) {
if ( presetValue === undefined ) {
return 0;
}
const slug =
parseFloat( presetValue, 10 ) === 0
? '0'
: getPresetSlug( presetValue );
const sliderValue = presets.findIndex( ( size ) => {
return String( size.slug ) === slug;
} );
// Returning NaN rather than undefined as undefined makes range control thumb sit in center
return sliderValue !== -1 ? sliderValue : NaN;
}
/**
* Converts a preset into a custom value.
*
* @param {string} value Value to convert
* @param {Array} presets Array of the current radius preset objects
*
* @return {string} Mapping of the radius preset to its equivalent custom value.
*/
export function getCustomValueFromPreset( value, presets ) {
if ( ! isValuePreset( value ) ) {
return value;
}
const slug = parseFloat( value, 10 ) === 0 ? '0' : getPresetSlug( value );
const radiusSize = presets.find( ( size ) => String( size.slug ) === slug );
return radiusSize?.size;
}
/**
* Converts a control value into a preset value.
*
* @param {number} controlValue to convert to preset value.
* @param {string} controlType Type of control
* @param {Array} presets Array of current radius preset value objects.
*
* @return {string} The custom value for use in Range control.
*/
export function getPresetValueFromControlValue(
controlValue,
controlType,
presets
) {
const size = parseInt( controlValue, 10 );
if ( controlType === 'selectList' ) {
if ( size === 0 ) {
return undefined;
}
} else if ( size === 0 ) {
return '0';
}
return `var:preset|border-radius|${ presets[ controlValue ]?.slug }`;
}
/**
* Converts a custom value to preset value if one can be found.
*
* Returns value as-is if no match is found.
*
* @param {string} value Value to convert
* @param {Array} presets Array of the current border radius preset objects
*
* @return {string} The preset value if it can be found.
*/
export function getPresetValueFromCustomValue( value, presets ) {
// Return value as-is if it is undefined or is already a preset, or '0';
if ( ! value || isValuePreset( value ) || value === '0' ) {
return value;
}
const spacingMatch = presets.find(
( size ) => String( size.size ) === String( value )
);
if ( spacingMatch?.slug ) {
return `var:preset|border-radius|${ spacingMatch.slug }`;
}
return value;
}
/**
* Converts all preset values in a values object to their custom equivalents.
*
* @param {Object} values Values object to convert
* @param {Array} presets Array of current border radius preset objects
*
* @return {Object} Values with presets converted to custom values
*/
export function convertPresetsToCustomValues( values, presets ) {
if ( ! values || typeof values !== 'object' ) {
return values;
}
const converted = {};
Object.keys( values ).forEach( ( key ) => {
const value = values[ key ];
if ( isValuePreset( value ) ) {
const customValue = getCustomValueFromPreset( value, presets );
converted[ key ] = customValue !== undefined ? customValue : value;
} else {
converted[ key ] = value;
}
} );
return converted;
}