@wordpress/components 30.6.1-next.ff1cebbba.0 → 30.7.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 (147) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/build/color-palette/styles.js +2 -12
  3. package/build/color-palette/styles.js.map +2 -2
  4. package/build/combobox-control/index.js +1 -2
  5. package/build/combobox-control/index.js.map +2 -2
  6. package/build/custom-select-control-v2/custom-select.js +2 -2
  7. package/build/custom-select-control-v2/custom-select.js.map +2 -2
  8. package/build/date-time/date/styles.js +9 -9
  9. package/build/date-time/date/styles.js.map +2 -2
  10. package/build/focal-point-picker/index.js +21 -10
  11. package/build/focal-point-picker/index.js.map +3 -3
  12. package/build/focal-point-picker/styles/focal-point-picker-style.js +20 -11
  13. package/build/focal-point-picker/styles/focal-point-picker-style.js.map +2 -2
  14. package/build/font-size-picker/font-size-picker-select.js +20 -19
  15. package/build/font-size-picker/font-size-picker-select.js.map +3 -3
  16. package/build/font-size-picker/font-size-picker-toggle-group.js +27 -3
  17. package/build/font-size-picker/font-size-picker-toggle-group.js.map +2 -2
  18. package/build/font-size-picker/index.js +23 -11
  19. package/build/font-size-picker/index.js.map +2 -2
  20. package/build/font-size-picker/styles.js +30 -13
  21. package/build/font-size-picker/styles.js.map +3 -3
  22. package/build/font-size-picker/utils.js +11 -0
  23. package/build/font-size-picker/utils.js.map +2 -2
  24. package/build/palette-edit/styles.js +9 -9
  25. package/build/palette-edit/styles.js.map +2 -2
  26. package/build/popover/index.js +13 -2
  27. package/build/popover/index.js.map +2 -2
  28. package/build/tabs/styles.js +5 -5
  29. package/build/tabs/styles.js.map +1 -1
  30. package/build/tools-panel/styles.js +14 -22
  31. package/build/tools-panel/styles.js.map +2 -2
  32. package/build/utils/base-label.js +12 -12
  33. package/build/utils/base-label.js.map +3 -3
  34. package/build/utils/config-values.js +2 -0
  35. package/build/utils/config-values.js.map +2 -2
  36. package/build-module/color-palette/styles.js +2 -12
  37. package/build-module/color-palette/styles.js.map +2 -2
  38. package/build-module/combobox-control/index.js +1 -2
  39. package/build-module/combobox-control/index.js.map +2 -2
  40. package/build-module/custom-select-control-v2/custom-select.js +1 -1
  41. package/build-module/custom-select-control-v2/custom-select.js.map +1 -1
  42. package/build-module/date-time/date/styles.js +9 -9
  43. package/build-module/date-time/date/styles.js.map +2 -2
  44. package/build-module/focal-point-picker/index.js +23 -12
  45. package/build-module/focal-point-picker/index.js.map +2 -2
  46. package/build-module/focal-point-picker/styles/focal-point-picker-style.js +20 -12
  47. package/build-module/focal-point-picker/styles/focal-point-picker-style.js.map +2 -2
  48. package/build-module/font-size-picker/font-size-picker-select.js +21 -10
  49. package/build-module/font-size-picker/font-size-picker-select.js.map +2 -2
  50. package/build-module/font-size-picker/font-size-picker-toggle-group.js +27 -3
  51. package/build-module/font-size-picker/font-size-picker-toggle-group.js.map +2 -2
  52. package/build-module/font-size-picker/index.js +23 -11
  53. package/build-module/font-size-picker/index.js.map +2 -2
  54. package/build-module/font-size-picker/styles.js +28 -12
  55. package/build-module/font-size-picker/styles.js.map +2 -2
  56. package/build-module/font-size-picker/utils.js +10 -0
  57. package/build-module/font-size-picker/utils.js.map +2 -2
  58. package/build-module/palette-edit/styles.js +9 -9
  59. package/build-module/palette-edit/styles.js.map +2 -2
  60. package/build-module/popover/index.js +13 -2
  61. package/build-module/popover/index.js.map +2 -2
  62. package/build-module/tabs/styles.js +5 -5
  63. package/build-module/tabs/styles.js.map +1 -1
  64. package/build-module/tools-panel/styles.js +14 -22
  65. package/build-module/tools-panel/styles.js.map +2 -2
  66. package/build-module/utils/base-label.js +2 -12
  67. package/build-module/utils/base-label.js.map +2 -2
  68. package/build-module/utils/config-values.js +2 -0
  69. package/build-module/utils/config-values.js.map +2 -2
  70. package/build-style/style-rtl.css +11 -5
  71. package/build-style/style.css +11 -5
  72. package/build-types/card/card-body/component.d.ts.map +1 -1
  73. package/build-types/card/card-body/hook.d.ts.map +1 -1
  74. package/build-types/card/card-footer/component.d.ts +1 -3
  75. package/build-types/card/card-footer/component.d.ts.map +1 -1
  76. package/build-types/card/card-footer/hook.d.ts +6 -0
  77. package/build-types/card/card-footer/hook.d.ts.map +1 -1
  78. package/build-types/card/card-header/component.d.ts +1 -1
  79. package/build-types/card/card-header/component.d.ts.map +1 -1
  80. package/build-types/card/card-header/hook.d.ts +7 -0
  81. package/build-types/card/card-header/hook.d.ts.map +1 -1
  82. package/build-types/card/card-media/hook.d.ts.map +1 -1
  83. package/build-types/card/types.d.ts +3 -8
  84. package/build-types/card/types.d.ts.map +1 -1
  85. package/build-types/color-palette/styles.d.ts.map +1 -1
  86. package/build-types/combobox-control/index.d.ts.map +1 -1
  87. package/build-types/combobox-control/stories/index.story.d.ts.map +1 -1
  88. package/build-types/date-time/date/styles.d.ts.map +1 -1
  89. package/build-types/focal-point-picker/index.d.ts +1 -1
  90. package/build-types/focal-point-picker/index.d.ts.map +1 -1
  91. package/build-types/focal-point-picker/styles/focal-point-picker-style.d.ts +253 -0
  92. package/build-types/focal-point-picker/styles/focal-point-picker-style.d.ts.map +1 -1
  93. package/build-types/font-size-picker/font-size-picker-select.d.ts +3 -0
  94. package/build-types/font-size-picker/font-size-picker-select.d.ts.map +1 -1
  95. package/build-types/font-size-picker/font-size-picker-toggle-group.d.ts.map +1 -1
  96. package/build-types/font-size-picker/index.d.ts.map +1 -1
  97. package/build-types/font-size-picker/styles.d.ts +3 -0
  98. package/build-types/font-size-picker/styles.d.ts.map +1 -1
  99. package/build-types/font-size-picker/test/font-size-picker-select.d.ts +2 -0
  100. package/build-types/font-size-picker/test/font-size-picker-select.d.ts.map +1 -0
  101. package/build-types/font-size-picker/test/font-size-picker-toggle-group.d.ts +2 -0
  102. package/build-types/font-size-picker/test/font-size-picker-toggle-group.d.ts.map +1 -0
  103. package/build-types/font-size-picker/types.d.ts +18 -4
  104. package/build-types/font-size-picker/types.d.ts.map +1 -1
  105. package/build-types/font-size-picker/utils.d.ts +10 -1
  106. package/build-types/font-size-picker/utils.d.ts.map +1 -1
  107. package/build-types/popover/index.d.ts.map +1 -1
  108. package/build-types/utils/base-label.d.ts.map +1 -1
  109. package/build-types/utils/config-values.d.ts +1 -0
  110. package/package.json +20 -20
  111. package/src/badge/styles.scss +1 -0
  112. package/src/button/style.scss +4 -1
  113. package/src/card/types.ts +3 -9
  114. package/src/color-palette/styles.ts +2 -1
  115. package/src/combobox-control/index.tsx +1 -4
  116. package/src/combobox-control/stories/index.story.tsx +0 -1
  117. package/src/combobox-control/test/index.tsx +20 -7
  118. package/src/custom-select-control-v2/custom-select.tsx +1 -1
  119. package/src/date-time/date/styles.ts +1 -0
  120. package/src/dimension-control/test/__snapshots__/index.test.js.snap +4 -4
  121. package/src/dropdown-menu/style.scss +1 -0
  122. package/src/focal-point-picker/index.tsx +26 -16
  123. package/src/focal-point-picker/styles/focal-point-picker-style.ts +11 -1
  124. package/src/font-size-picker/README.md +10 -0
  125. package/src/font-size-picker/font-size-picker-select.tsx +44 -11
  126. package/src/font-size-picker/font-size-picker-toggle-group.tsx +58 -4
  127. package/src/font-size-picker/index.tsx +44 -19
  128. package/src/font-size-picker/styles.ts +9 -0
  129. package/src/font-size-picker/test/font-size-picker-select.tsx +221 -0
  130. package/src/font-size-picker/test/font-size-picker-toggle-group.tsx +275 -0
  131. package/src/font-size-picker/test/index.tsx +460 -2
  132. package/src/font-size-picker/types.ts +24 -4
  133. package/src/font-size-picker/utils.ts +23 -1
  134. package/src/menu-group/style.scss +1 -1
  135. package/src/menu-item/style.scss +1 -0
  136. package/src/modal/style.scss +1 -1
  137. package/src/palette-edit/styles.ts +1 -1
  138. package/src/panel/style.scss +1 -1
  139. package/src/popover/index.tsx +23 -2
  140. package/src/tab-panel/style.scss +1 -1
  141. package/src/tabs/styles.ts +1 -1
  142. package/src/toggle-group-control/test/__snapshots__/index.tsx.snap +4 -4
  143. package/src/tools-panel/styles.ts +2 -2
  144. package/src/utils/base-label.ts +6 -1
  145. package/src/utils/config-values.js +1 -0
  146. package/src/validated-form-controls/style.scss +1 -1
  147. package/tsconfig.tsbuildinfo +1 -1
@@ -2,16 +2,17 @@
2
2
  * WordPress dependencies
3
3
  */
4
4
  import { __, sprintf } from '@wordpress/i18n';
5
+ import { useMemo } from '@wordpress/element';
5
6
 
6
7
  /**
7
8
  * Internal dependencies
8
9
  */
9
- import CustomSelectControl from '../custom-select-control';
10
10
  import type {
11
11
  FontSizePickerSelectProps,
12
12
  FontSizePickerSelectOption,
13
13
  } from './types';
14
- import { isSimpleCssValue } from './utils';
14
+ import { generateFontSizeHint } from './utils';
15
+ import { StyledCustomSelectControl } from './styles';
15
16
 
16
17
  const DEFAULT_OPTION: FontSizePickerSelectOption = {
17
18
  key: 'default',
@@ -20,15 +21,19 @@ const DEFAULT_OPTION: FontSizePickerSelectOption = {
20
21
  };
21
22
 
22
23
  const FontSizePickerSelect = ( props: FontSizePickerSelectProps ) => {
23
- const { __next40pxDefaultSize, fontSizes, value, size, onChange } = props;
24
+ const {
25
+ __next40pxDefaultSize,
26
+ fontSizes,
27
+ value,
28
+ size,
29
+ valueMode = 'literal',
30
+ onChange,
31
+ } = props;
24
32
 
25
33
  const options: FontSizePickerSelectOption[] = [
26
34
  DEFAULT_OPTION,
27
35
  ...fontSizes.map( ( fontSize ) => {
28
- let hint;
29
- if ( isSimpleCssValue( fontSize.size ) ) {
30
- hint = String( fontSize.size );
31
- }
36
+ const hint = generateFontSizeHint( fontSize );
32
37
  return {
33
38
  key: fontSize.slug,
34
39
  name: fontSize.name || fontSize.slug,
@@ -38,11 +43,30 @@ const FontSizePickerSelect = ( props: FontSizePickerSelectProps ) => {
38
43
  } ),
39
44
  ];
40
45
 
41
- const selectedOption =
42
- options.find( ( option ) => option.value === value ) ?? DEFAULT_OPTION;
46
+ const selectedOption = useMemo( () => {
47
+ if ( value === undefined ) {
48
+ return DEFAULT_OPTION;
49
+ }
50
+
51
+ // If valueMode is 'slug', find by slug
52
+ if ( valueMode === 'slug' ) {
53
+ const optionBySlug = options.find(
54
+ ( option ) => option.key === value
55
+ );
56
+ if ( optionBySlug ) {
57
+ return optionBySlug;
58
+ }
59
+ }
60
+
61
+ // If valueMode is 'literal', find by value (size)
62
+ return (
63
+ options.find( ( option ) => option.value === value ) ??
64
+ DEFAULT_OPTION
65
+ );
66
+ }, [ value, valueMode, options ] );
43
67
 
44
68
  return (
45
- <CustomSelectControl
69
+ <StyledCustomSelectControl
46
70
  __next40pxDefaultSize={ __next40pxDefaultSize }
47
71
  __shouldNotWarnDeprecated36pxSize
48
72
  className="components-font-size-picker__select"
@@ -61,7 +85,16 @@ const FontSizePickerSelect = ( props: FontSizePickerSelectProps ) => {
61
85
  }: {
62
86
  selectedItem: FontSizePickerSelectOption;
63
87
  } ) => {
64
- onChange( selectedItem.value );
88
+ // Find the corresponding FontSize object
89
+ const matchingFontSize =
90
+ selectedItem.key === 'default'
91
+ ? undefined
92
+ : fontSizes.find(
93
+ ( fontSize ) =>
94
+ fontSize.slug === selectedItem.key
95
+ );
96
+
97
+ onChange( selectedItem.value, matchingFontSize );
65
98
  } }
66
99
  size={ size }
67
100
  />
@@ -14,7 +14,46 @@ import { T_SHIRT_ABBREVIATIONS, T_SHIRT_NAMES } from './constants';
14
14
  import type { FontSizePickerToggleGroupProps } from './types';
15
15
 
16
16
  const FontSizePickerToggleGroup = ( props: FontSizePickerToggleGroupProps ) => {
17
- const { fontSizes, value, __next40pxDefaultSize, size, onChange } = props;
17
+ const {
18
+ fontSizes,
19
+ value,
20
+ valueMode = 'literal',
21
+ __next40pxDefaultSize,
22
+ size,
23
+ onChange,
24
+ } = props;
25
+
26
+ // Find the current value based on valueMode
27
+ const currentValue = ( () => {
28
+ if ( ! value ) {
29
+ return undefined;
30
+ }
31
+
32
+ // If valueMode is 'slug', the value is already the slug
33
+ if ( valueMode === 'slug' ) {
34
+ return String( value );
35
+ }
36
+
37
+ // If valueMode is 'literal', find the font size by size value
38
+ // If multiple font sizes have the same size value, we can't distinguish them
39
+ // without additional information, so we return undefined to avoid incorrect selection
40
+ const matchingFontSizes = fontSizes.filter(
41
+ ( fontSize ) => fontSize.size === value
42
+ );
43
+
44
+ // If there are multiple matches, return undefined to avoid selecting the wrong font size
45
+ if ( matchingFontSizes.length > 1 ) {
46
+ return undefined;
47
+ }
48
+
49
+ // Find the font size by size value
50
+ const fontSizeBySize = fontSizes.find(
51
+ ( fontSize ) => fontSize.size === value
52
+ );
53
+
54
+ return fontSizeBySize?.slug;
55
+ } )();
56
+
18
57
  return (
19
58
  <ToggleGroupControl
20
59
  __nextHasNoMarginBottom
@@ -22,15 +61,30 @@ const FontSizePickerToggleGroup = ( props: FontSizePickerToggleGroupProps ) => {
22
61
  __shouldNotWarnDeprecated36pxSize
23
62
  label={ __( 'Font size' ) }
24
63
  hideLabelFromVision
25
- value={ value }
26
- onChange={ onChange }
64
+ value={ currentValue }
65
+ onChange={ ( newSlug: string | number | undefined ) => {
66
+ if ( newSlug === undefined ) {
67
+ onChange( undefined );
68
+ } else {
69
+ // Find the font size by slug
70
+ const selectedFontSize = fontSizes.find(
71
+ ( fontSize ) => fontSize.slug === String( newSlug )
72
+ );
73
+ if ( selectedFontSize ) {
74
+ onChange(
75
+ selectedFontSize.size as number | string,
76
+ selectedFontSize
77
+ );
78
+ }
79
+ }
80
+ } }
27
81
  isBlock
28
82
  size={ size }
29
83
  >
30
84
  { fontSizes.map( ( fontSize, index ) => (
31
85
  <ToggleGroupControlOption
32
86
  key={ fontSize.slug }
33
- value={ fontSize.size }
87
+ value={ fontSize.slug }
34
88
  label={ T_SHIRT_ABBREVIATIONS[ index ] }
35
89
  aria-label={ fontSize.name || T_SHIRT_NAMES[ index ] }
36
90
  showTooltip
@@ -46,6 +46,7 @@ const UnforwardedFontSizePicker = (
46
46
  size = 'default',
47
47
  units: unitsProp = DEFAULT_UNITS,
48
48
  value,
49
+ valueMode = 'literal',
49
50
  withSlider = false,
50
51
  withReset = true,
51
52
  } = props;
@@ -59,15 +60,32 @@ const UnforwardedFontSizePicker = (
59
60
  availableUnits: unitsProp,
60
61
  } );
61
62
 
62
- const selectedFontSize = fontSizes.find(
63
- ( fontSize ) => fontSize.size === value
64
- );
63
+ const selectedFontSize = ( () => {
64
+ if ( ! value ) {
65
+ return undefined;
66
+ }
67
+
68
+ // If valueMode is 'slug', find by slug
69
+ if ( valueMode === 'slug' ) {
70
+ return fontSizes.find( ( fontSize ) => fontSize.slug === value );
71
+ }
72
+
73
+ // If valueMode is 'literal', find by size value
74
+ return fontSizes.find( ( fontSize ) => fontSize.size === value );
75
+ } )();
65
76
  const isCustomValue = !! value && ! selectedFontSize;
66
77
 
67
78
  // Initially request a custom picker if the value is not from the predef list.
68
79
  const [ userRequestedCustom, setUserRequestedCustom ] =
69
80
  useState( isCustomValue );
70
81
 
82
+ // Resolve the literal value to use in custom controls when operating in slug mode.
83
+ // When `valueMode` is 'slug', the `value` prop contains the slug of the
84
+ // selected preset. In that case, the custom input should reflect the preset's
85
+ // actual size value so it pre-populates correctly after clicking "Set custom size".
86
+ const resolvedValueForControls =
87
+ valueMode === 'slug' ? selectedFontSize?.size : value;
88
+
71
89
  let currentPickerType;
72
90
  if ( ! disableCustomFontSizes && userRequestedCustom ) {
73
91
  // While showing the custom value picker, switch back to predef only if
@@ -88,10 +106,11 @@ const UnforwardedFontSizePicker = (
88
106
  // operates in a legacy "unitless" mode where UnitControl can only be used
89
107
  // to select px values and onChange() is always called with number values.
90
108
  const hasUnits =
91
- typeof value === 'string' || typeof fontSizes[ 0 ]?.size === 'string';
109
+ typeof resolvedValueForControls === 'string' ||
110
+ typeof fontSizes[ 0 ]?.size === 'string';
92
111
 
93
112
  const [ valueQuantity, valueUnit ] = parseQuantityAndUnitFromRawValue(
94
- value,
113
+ resolvedValueForControls,
95
114
  units
96
115
  );
97
116
  const isValueUnitRelative =
@@ -139,18 +158,16 @@ const UnforwardedFontSizePicker = (
139
158
  __next40pxDefaultSize={ __next40pxDefaultSize }
140
159
  fontSizes={ fontSizes }
141
160
  value={ value }
161
+ valueMode={ valueMode }
142
162
  disableCustomFontSizes={ disableCustomFontSizes }
143
163
  size={ size }
144
- onChange={ ( newValue ) => {
164
+ onChange={ ( newValue, selectedItem ) => {
145
165
  if ( newValue === undefined ) {
146
- onChange?.( undefined );
166
+ onChange?.( undefined, selectedItem );
147
167
  } else {
148
168
  onChange?.(
149
169
  hasUnits ? newValue : Number( newValue ),
150
- fontSizes.find(
151
- ( fontSize ) =>
152
- fontSize.size === newValue
153
- )
170
+ selectedItem
154
171
  );
155
172
  }
156
173
  } }
@@ -161,18 +178,16 @@ const UnforwardedFontSizePicker = (
161
178
  <FontSizePickerToggleGroup
162
179
  fontSizes={ fontSizes }
163
180
  value={ value }
181
+ valueMode={ valueMode }
164
182
  __next40pxDefaultSize={ __next40pxDefaultSize }
165
183
  size={ size }
166
- onChange={ ( newValue ) => {
184
+ onChange={ ( newValue, selectedItem ) => {
167
185
  if ( newValue === undefined ) {
168
- onChange?.( undefined );
186
+ onChange?.( undefined, selectedItem );
169
187
  } else {
170
188
  onChange?.(
171
189
  hasUnits ? newValue : Number( newValue ),
172
- fontSizes.find(
173
- ( fontSize ) =>
174
- fontSize.size === newValue
175
- )
190
+ selectedItem
176
191
  );
177
192
  }
178
193
  } }
@@ -187,11 +202,21 @@ const UnforwardedFontSizePicker = (
187
202
  label={ __( 'Font size' ) }
188
203
  labelPosition="top"
189
204
  hideLabelFromVision
190
- value={ value }
205
+ value={
206
+ hasUnits
207
+ ? `${ valueQuantity ?? '' }${
208
+ valueUnit ?? ''
209
+ }`
210
+ : resolvedValueForControls
211
+ }
191
212
  onChange={ ( newValue ) => {
192
213
  setUserRequestedCustom( true );
193
214
 
194
- if ( newValue === undefined ) {
215
+ // Treat clearing the input (empty string) as a reset
216
+ if (
217
+ newValue === undefined ||
218
+ newValue === ''
219
+ ) {
195
220
  onChange?.( undefined );
196
221
  } else {
197
222
  onChange?.(
@@ -8,6 +8,7 @@ import styled from '@emotion/styled';
8
8
  */
9
9
  import BaseControl from '../base-control';
10
10
  import Button from '../button';
11
+ import CustomSelectControl from '../custom-select-control';
11
12
  import { HStack } from '../h-stack';
12
13
  import { space } from '../utils/space';
13
14
 
@@ -32,3 +33,11 @@ export const HeaderLabel = styled( BaseControl.VisualLabel )`
32
33
  justify-content: flex-start;
33
34
  margin-bottom: 0;
34
35
  `;
36
+
37
+ // Custom styled component to force line break between name and hint while keeping checkmark on the right
38
+ export const StyledCustomSelectControl = styled( CustomSelectControl )`
39
+ .components-custom-select-control__item
40
+ .components-custom-select-control__item-hint {
41
+ width: 100%;
42
+ }
43
+ `;
@@ -0,0 +1,221 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { screen } from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
6
+ import { render } from '@ariakit/test/react';
7
+
8
+ /**
9
+ * Internal dependencies
10
+ */
11
+ import FontSizePickerSelect from '../font-size-picker-select';
12
+ import type { FontSize } from '../types';
13
+
14
+ describe( 'FontSizePickerSelect', () => {
15
+ const fontSizes: FontSize[] = [
16
+ {
17
+ slug: 'small',
18
+ name: 'Small',
19
+ size: '12px',
20
+ },
21
+ {
22
+ slug: 'medium',
23
+ name: 'Medium',
24
+ size: '16px',
25
+ },
26
+ {
27
+ slug: 'large',
28
+ name: 'Large',
29
+ size: '20px',
30
+ },
31
+ ];
32
+
33
+ describe( 'valueMode prop', () => {
34
+ it( 'should find font size by size value when valueMode is literal', async () => {
35
+ const onChange = jest.fn();
36
+ await render(
37
+ <FontSizePickerSelect
38
+ fontSizes={ fontSizes }
39
+ value="16px"
40
+ valueMode="literal"
41
+ onChange={ onChange }
42
+ onSelectCustom={ jest.fn() }
43
+ disableCustomFontSizes={ false }
44
+ __next40pxDefaultSize={ false }
45
+ size="default"
46
+ />
47
+ );
48
+ // Should select the medium option (16px)
49
+ expect(
50
+ screen.getByRole( 'combobox', { name: 'Font size' } )
51
+ ).toHaveTextContent( 'Medium' );
52
+ } );
53
+
54
+ it( 'should find font size by slug when valueMode is slug', async () => {
55
+ const onChange = jest.fn();
56
+ await render(
57
+ <FontSizePickerSelect
58
+ fontSizes={ fontSizes }
59
+ value="medium"
60
+ valueMode="slug"
61
+ onChange={ onChange }
62
+ onSelectCustom={ jest.fn() }
63
+ disableCustomFontSizes={ false }
64
+ __next40pxDefaultSize={ false }
65
+ size="default"
66
+ />
67
+ );
68
+ // Should select the medium option
69
+ expect(
70
+ screen.getByRole( 'combobox', { name: 'Font size' } )
71
+ ).toHaveTextContent( 'Medium' );
72
+ } );
73
+
74
+ it( 'should handle undefined value', async () => {
75
+ const onChange = jest.fn();
76
+ await render(
77
+ <FontSizePickerSelect
78
+ fontSizes={ fontSizes }
79
+ value={ undefined }
80
+ valueMode="literal"
81
+ onChange={ onChange }
82
+ onSelectCustom={ jest.fn() }
83
+ disableCustomFontSizes={ false }
84
+ __next40pxDefaultSize={ false }
85
+ size="default"
86
+ />
87
+ );
88
+ // Should show default option
89
+ expect(
90
+ screen.getByRole( 'combobox', { name: 'Font size' } )
91
+ ).toHaveTextContent( 'Default' );
92
+ } );
93
+
94
+ it( 'should handle empty string value', async () => {
95
+ const onChange = jest.fn();
96
+ await render(
97
+ <FontSizePickerSelect
98
+ fontSizes={ fontSizes }
99
+ value=""
100
+ valueMode="literal"
101
+ onChange={ onChange }
102
+ onSelectCustom={ jest.fn() }
103
+ disableCustomFontSizes={ false }
104
+ __next40pxDefaultSize={ false }
105
+ size="default"
106
+ />
107
+ );
108
+ // Should show default option
109
+ expect(
110
+ screen.getByRole( 'combobox', { name: 'Font size' } )
111
+ ).toHaveTextContent( 'Default' );
112
+ } );
113
+ } );
114
+
115
+ describe( 'onChange callback', () => {
116
+ it( 'should call onChange with FontSize object as second parameter', async () => {
117
+ const user = userEvent.setup();
118
+ const onChange = jest.fn();
119
+ await render(
120
+ <FontSizePickerSelect
121
+ fontSizes={ fontSizes }
122
+ onChange={ onChange }
123
+ onSelectCustom={ jest.fn() }
124
+ disableCustomFontSizes={ false }
125
+ __next40pxDefaultSize={ false }
126
+ size="default"
127
+ />
128
+ );
129
+ await user.click(
130
+ screen.getByRole( 'combobox', { name: 'Font size' } )
131
+ );
132
+ await user.click(
133
+ screen.getByRole( 'option', { name: 'Small 12px' } )
134
+ );
135
+ expect( onChange ).toHaveBeenCalledWith( '12px', fontSizes[ 0 ] );
136
+ } );
137
+
138
+ it( 'should call onChange with undefined as second parameter for default option', async () => {
139
+ const user = userEvent.setup();
140
+ const onChange = jest.fn();
141
+ await render(
142
+ <FontSizePickerSelect
143
+ fontSizes={ fontSizes }
144
+ value="16px" // Start with a selected value
145
+ onChange={ onChange }
146
+ onSelectCustom={ jest.fn() }
147
+ disableCustomFontSizes={ false }
148
+ __next40pxDefaultSize={ false }
149
+ size="default"
150
+ />
151
+ );
152
+ await user.click(
153
+ screen.getByRole( 'combobox', { name: 'Font size' } )
154
+ );
155
+ await user.click(
156
+ screen.getByRole( 'option', { name: 'Default' } )
157
+ );
158
+ expect( onChange ).toHaveBeenCalledWith( undefined, undefined );
159
+ } );
160
+ } );
161
+
162
+ describe( 'edge cases', () => {
163
+ const fontSizesWithDuplicates: FontSize[] = [
164
+ {
165
+ slug: 'small-1',
166
+ name: 'Small 1',
167
+ size: '12px',
168
+ },
169
+ {
170
+ slug: 'small-2',
171
+ name: 'Small 2',
172
+ size: '12px',
173
+ },
174
+ {
175
+ slug: 'medium',
176
+ name: 'Medium',
177
+ size: '16px',
178
+ },
179
+ ];
180
+
181
+ it( 'should handle multiple font sizes with same value in literal mode', async () => {
182
+ const onChange = jest.fn();
183
+ await render(
184
+ <FontSizePickerSelect
185
+ fontSizes={ fontSizesWithDuplicates }
186
+ value="12px"
187
+ valueMode="literal"
188
+ onChange={ onChange }
189
+ onSelectCustom={ jest.fn() }
190
+ disableCustomFontSizes={ false }
191
+ __next40pxDefaultSize={ false }
192
+ size="default"
193
+ />
194
+ );
195
+ // Should show the first matching font size when there are multiple matches
196
+ expect(
197
+ screen.getByRole( 'combobox', { name: 'Font size' } )
198
+ ).toHaveTextContent( 'Small 1' );
199
+ } );
200
+
201
+ it( 'should handle multiple font sizes with same value in slug mode', async () => {
202
+ const onChange = jest.fn();
203
+ await render(
204
+ <FontSizePickerSelect
205
+ fontSizes={ fontSizesWithDuplicates }
206
+ value="small-1"
207
+ valueMode="slug"
208
+ onChange={ onChange }
209
+ onSelectCustom={ jest.fn() }
210
+ disableCustomFontSizes={ false }
211
+ __next40pxDefaultSize={ false }
212
+ size="default"
213
+ />
214
+ );
215
+ // Should select the specific font size by slug
216
+ expect(
217
+ screen.getByRole( 'combobox', { name: 'Font size' } )
218
+ ).toHaveTextContent( 'Small 1' );
219
+ } );
220
+ } );
221
+ } );