@wordpress/edit-site 5.28.1 → 5.28.3

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 (130) hide show
  1. package/build/components/add-new-pattern/index.js +18 -8
  2. package/build/components/add-new-pattern/index.js.map +1 -1
  3. package/build/components/editor/index.js +1 -1
  4. package/build/components/editor/index.js.map +1 -1
  5. package/build/components/global-styles/block-preview-panel.js +2 -2
  6. package/build/components/global-styles/block-preview-panel.js.map +1 -1
  7. package/build/components/global-styles/font-library-modal/collection-font-details.js +1 -1
  8. package/build/components/global-styles/font-library-modal/collection-font-details.js.map +1 -1
  9. package/build/components/global-styles/font-library-modal/context.js +1 -1
  10. package/build/components/global-styles/font-library-modal/context.js.map +1 -1
  11. package/build/components/global-styles/font-library-modal/font-collection.js +84 -7
  12. package/build/components/global-styles/font-library-modal/font-collection.js.map +1 -1
  13. package/build/components/global-styles/font-library-modal/google-fonts-confirm-dialog.js +1 -1
  14. package/build/components/global-styles/font-library-modal/google-fonts-confirm-dialog.js.map +1 -1
  15. package/build/components/global-styles/font-library-modal/installed-fonts.js +10 -7
  16. package/build/components/global-styles/font-library-modal/installed-fonts.js.map +1 -1
  17. package/build/components/global-styles/font-library-modal/utils/index.js +1 -1
  18. package/build/components/global-styles/font-library-modal/utils/index.js.map +1 -1
  19. package/build/components/global-styles/font-library-modal/utils/make-families-from-faces.js +14 -1
  20. package/build/components/global-styles/font-library-modal/utils/make-families-from-faces.js.map +1 -1
  21. package/build/components/global-styles/font-library-modal/utils/preview-styles.js +64 -8
  22. package/build/components/global-styles/font-library-modal/utils/preview-styles.js.map +1 -1
  23. package/build/components/global-styles/screen-revisions/revisions-buttons.js +6 -4
  24. package/build/components/global-styles/screen-revisions/revisions-buttons.js.map +1 -1
  25. package/build/components/layout/index.js +8 -3
  26. package/build/components/layout/index.js.map +1 -1
  27. package/build/components/layout/router.js +26 -5
  28. package/build/components/layout/router.js.map +1 -1
  29. package/build/components/media/index.js +3 -3
  30. package/build/components/media/index.js.map +1 -1
  31. package/build/components/page-patterns/use-patterns.js +10 -5
  32. package/build/components/page-patterns/use-patterns.js.map +1 -1
  33. package/build/components/page-templates-template-parts/index.js +1 -0
  34. package/build/components/page-templates-template-parts/index.js.map +1 -1
  35. package/build/components/sidebar/index.js +10 -3
  36. package/build/components/sidebar/index.js.map +1 -1
  37. package/build/components/sidebar-dataviews/default-views.js +1 -1
  38. package/build/components/sidebar-dataviews/default-views.js.map +1 -1
  39. package/build/components/sidebar-navigation-screen-pages/index.js +3 -1
  40. package/build/components/sidebar-navigation-screen-pages/index.js.map +1 -1
  41. package/build/hooks/commands/use-edit-mode-commands.js +8 -8
  42. package/build/hooks/commands/use-edit-mode-commands.js.map +1 -1
  43. package/build/store/private-actions.js +3 -1
  44. package/build/store/private-actions.js.map +1 -1
  45. package/build-module/components/add-new-pattern/index.js +18 -8
  46. package/build-module/components/add-new-pattern/index.js.map +1 -1
  47. package/build-module/components/editor/index.js +1 -1
  48. package/build-module/components/editor/index.js.map +1 -1
  49. package/build-module/components/global-styles/block-preview-panel.js +2 -2
  50. package/build-module/components/global-styles/block-preview-panel.js.map +1 -1
  51. package/build-module/components/global-styles/font-library-modal/collection-font-details.js +1 -1
  52. package/build-module/components/global-styles/font-library-modal/collection-font-details.js.map +1 -1
  53. package/build-module/components/global-styles/font-library-modal/context.js +1 -1
  54. package/build-module/components/global-styles/font-library-modal/context.js.map +1 -1
  55. package/build-module/components/global-styles/font-library-modal/font-collection.js +87 -10
  56. package/build-module/components/global-styles/font-library-modal/font-collection.js.map +1 -1
  57. package/build-module/components/global-styles/font-library-modal/google-fonts-confirm-dialog.js +1 -1
  58. package/build-module/components/global-styles/font-library-modal/google-fonts-confirm-dialog.js.map +1 -1
  59. package/build-module/components/global-styles/font-library-modal/installed-fonts.js +11 -8
  60. package/build-module/components/global-styles/font-library-modal/installed-fonts.js.map +1 -1
  61. package/build-module/components/global-styles/font-library-modal/utils/index.js +2 -2
  62. package/build-module/components/global-styles/font-library-modal/utils/index.js.map +1 -1
  63. package/build-module/components/global-styles/font-library-modal/utils/make-families-from-faces.js +13 -1
  64. package/build-module/components/global-styles/font-library-modal/utils/make-families-from-faces.js.map +1 -1
  65. package/build-module/components/global-styles/font-library-modal/utils/preview-styles.js +63 -8
  66. package/build-module/components/global-styles/font-library-modal/utils/preview-styles.js.map +1 -1
  67. package/build-module/components/global-styles/screen-revisions/revisions-buttons.js +6 -4
  68. package/build-module/components/global-styles/screen-revisions/revisions-buttons.js.map +1 -1
  69. package/build-module/components/layout/index.js +8 -3
  70. package/build-module/components/layout/index.js.map +1 -1
  71. package/build-module/components/layout/router.js +26 -5
  72. package/build-module/components/layout/router.js.map +1 -1
  73. package/build-module/components/media/index.js +3 -3
  74. package/build-module/components/media/index.js.map +1 -1
  75. package/build-module/components/page-patterns/use-patterns.js +10 -5
  76. package/build-module/components/page-patterns/use-patterns.js.map +1 -1
  77. package/build-module/components/page-templates-template-parts/index.js +1 -0
  78. package/build-module/components/page-templates-template-parts/index.js.map +1 -1
  79. package/build-module/components/sidebar/index.js +10 -3
  80. package/build-module/components/sidebar/index.js.map +1 -1
  81. package/build-module/components/sidebar-dataviews/default-views.js +2 -2
  82. package/build-module/components/sidebar-dataviews/default-views.js.map +1 -1
  83. package/build-module/components/sidebar-navigation-screen-pages/index.js +3 -1
  84. package/build-module/components/sidebar-navigation-screen-pages/index.js.map +1 -1
  85. package/build-module/hooks/commands/use-edit-mode-commands.js +8 -8
  86. package/build-module/hooks/commands/use-edit-mode-commands.js.map +1 -1
  87. package/build-module/store/private-actions.js +3 -1
  88. package/build-module/store/private-actions.js.map +1 -1
  89. package/build-style/style-rtl.css +33 -11
  90. package/build-style/style.css +33 -11
  91. package/package.json +42 -42
  92. package/src/components/add-new-pattern/index.js +27 -11
  93. package/src/components/editor/index.js +1 -0
  94. package/src/components/global-styles/block-preview-panel.js +2 -2
  95. package/src/components/global-styles/font-library-modal/collection-font-details.js +1 -1
  96. package/src/components/global-styles/font-library-modal/context.js +1 -1
  97. package/src/components/global-styles/font-library-modal/font-collection.js +118 -13
  98. package/src/components/global-styles/font-library-modal/google-fonts-confirm-dialog.js +1 -1
  99. package/src/components/global-styles/font-library-modal/installed-fonts.js +25 -23
  100. package/src/components/global-styles/font-library-modal/style.scss +2 -5
  101. package/src/components/global-styles/font-library-modal/utils/index.js +2 -2
  102. package/src/components/global-styles/font-library-modal/utils/make-families-from-faces.js +13 -1
  103. package/src/components/global-styles/font-library-modal/utils/preview-styles.js +72 -15
  104. package/src/components/global-styles/font-library-modal/utils/test/preview-styles.spec.js +60 -5
  105. package/src/components/global-styles/screen-revisions/revisions-buttons.js +7 -5
  106. package/src/components/global-styles/screen-revisions/style.scss +11 -6
  107. package/src/components/layout/index.js +47 -30
  108. package/src/components/layout/router.js +31 -2
  109. package/src/components/layout/style.scss +7 -0
  110. package/src/components/media/index.js +7 -9
  111. package/src/components/page-patterns/style.scss +10 -0
  112. package/src/components/page-patterns/use-patterns.js +13 -5
  113. package/src/components/page-templates-template-parts/index.js +1 -0
  114. package/src/components/page-templates-template-parts/style.scss +6 -0
  115. package/src/components/sidebar/index.js +14 -5
  116. package/src/components/sidebar-dataviews/default-views.js +2 -2
  117. package/src/components/sidebar-edit-mode/page-panels/style.scss +0 -1
  118. package/src/components/sidebar-navigation-screen-pages/index.js +10 -6
  119. package/src/hooks/commands/use-edit-mode-commands.js +14 -14
  120. package/src/store/private-actions.js +4 -0
  121. package/build/components/global-styles/font-library-modal/fonts-grid.js +0 -57
  122. package/build/components/global-styles/font-library-modal/fonts-grid.js.map +0 -1
  123. package/build/components/sidebar-navigation-screen-pages-dataviews/index.js +0 -92
  124. package/build/components/sidebar-navigation-screen-pages-dataviews/index.js.map +0 -1
  125. package/build-module/components/global-styles/font-library-modal/fonts-grid.js +0 -50
  126. package/build-module/components/global-styles/font-library-modal/fonts-grid.js.map +0 -1
  127. package/build-module/components/sidebar-navigation-screen-pages-dataviews/index.js +0 -84
  128. package/build-module/components/sidebar-navigation-screen-pages-dataviews/index.js.map +0 -1
  129. package/src/components/global-styles/font-library-modal/fonts-grid.js +0 -59
  130. package/src/components/sidebar-navigation-screen-pages-dataviews/index.js +0 -103
@@ -25,10 +25,11 @@ import {
25
25
  PATTERN_DEFAULT_CATEGORY,
26
26
  TEMPLATE_PART_POST_TYPE,
27
27
  } from '../../utils/constants';
28
- import usePatternCategories from '../sidebar-navigation-screen-patterns/use-pattern-categories';
29
28
 
30
29
  const { useHistory, useLocation } = unlock( routerPrivateApis );
31
- const { CreatePatternModal } = unlock( editPatternsPrivateApis );
30
+ const { CreatePatternModal, useAddPatternCategory } = unlock(
31
+ editPatternsPrivateApis
32
+ );
32
33
 
33
34
  export default function AddNewPattern() {
34
35
  const history = useHistory();
@@ -43,7 +44,6 @@ export default function AddNewPattern() {
43
44
  const { createSuccessNotice, createErrorNotice } =
44
45
  useDispatch( noticesStore );
45
46
  const patternUploadInputRef = useRef();
46
- const { patternCategories } = usePatternCategories();
47
47
 
48
48
  function handleCreatePattern( { pattern, categoryId } ) {
49
49
  setShowPatternModal( false );
@@ -97,6 +97,7 @@ export default function AddNewPattern() {
97
97
  title: __( 'Import pattern from JSON' ),
98
98
  } );
99
99
 
100
+ const { categoryMap, findOrCreateTerm } = useAddPatternCategory();
100
101
  return (
101
102
  <>
102
103
  <DropdownMenu
@@ -132,12 +133,23 @@ export default function AddNewPattern() {
132
133
  const file = event.target.files?.[ 0 ];
133
134
  if ( ! file ) return;
134
135
  try {
135
- const currentCategoryId =
136
- params.categoryType !== TEMPLATE_PART_POST_TYPE &&
137
- patternCategories.find(
138
- ( category ) =>
139
- category.name === params.categoryId
140
- )?.id;
136
+ let currentCategoryId;
137
+ // When we're not handling template parts, we should
138
+ // add or create the proper pattern category.
139
+ if ( params.categoryType !== TEMPLATE_PART_POST_TYPE ) {
140
+ const currentCategory = categoryMap
141
+ .values()
142
+ .find(
143
+ ( term ) => term.name === params.categoryId
144
+ );
145
+ if ( !! currentCategory ) {
146
+ currentCategoryId =
147
+ currentCategory.id ||
148
+ ( await findOrCreateTerm(
149
+ currentCategory.label
150
+ ) );
151
+ }
152
+ }
141
153
  const pattern = await createPatternFromFile(
142
154
  file,
143
155
  currentCategoryId
@@ -146,8 +158,12 @@ export default function AddNewPattern() {
146
158
  );
147
159
 
148
160
  // Navigate to the All patterns category for the newly created pattern
149
- // if we're not on that page already.
150
- if ( ! currentCategoryId ) {
161
+ // if we're not on that page already and if we're not in the `my-patterns`
162
+ // category.
163
+ if (
164
+ ! currentCategoryId &&
165
+ params.categoryId !== 'my-patterns'
166
+ ) {
151
167
  history.push( {
152
168
  path: `/patterns`,
153
169
  categoryType: PATTERN_TYPES.theme,
@@ -263,6 +263,7 @@ export default function Editor( { isLoading } ) {
263
263
  ( shouldShowListView && <ListViewSidebar /> ) )
264
264
  }
265
265
  sidebar={
266
+ ! isDistractionFree &&
266
267
  isEditMode &&
267
268
  isRightSidebarOpen && (
268
269
  <>
@@ -28,7 +28,7 @@ const BlockPreviewPanel = ( { name, variation = '' } ) => {
28
28
  }, [ name, blockExample, variation ] );
29
29
 
30
30
  const viewportWidth = blockExample?.viewportWidth ?? null;
31
- const previewHeight = '150px';
31
+ const previewHeight = 150;
32
32
 
33
33
  if ( ! blockExample ) {
34
34
  return null;
@@ -48,7 +48,7 @@ const BlockPreviewPanel = ( { name, variation = '' } ) => {
48
48
  {
49
49
  css: `
50
50
  body{
51
- min-height:${ previewHeight };
51
+ min-height:${ previewHeight }px;
52
52
  display:flex;align-items:center;justify-content:center;
53
53
  }
54
54
  `,
@@ -48,7 +48,7 @@ function CollectionFontDetails( {
48
48
  />
49
49
  ) ) }
50
50
  </VStack>
51
- <Spacer margin={ 8 } />
51
+ <Spacer margin={ 16 } />
52
52
  </>
53
53
  );
54
54
  }
@@ -392,7 +392,7 @@ function FontLibraryProvider( { children } ) {
392
392
  loadFontFaceInBrowser(
393
393
  face,
394
394
  getDisplaySrcFromFontFace( face.src ),
395
- 'iframe'
395
+ 'all'
396
396
  );
397
397
  } );
398
398
  }
@@ -1,11 +1,18 @@
1
1
  /**
2
2
  * WordPress dependencies
3
3
  */
4
- import { useContext, useEffect, useState, useMemo } from '@wordpress/element';
4
+ import {
5
+ useContext,
6
+ useEffect,
7
+ useState,
8
+ useMemo,
9
+ createInterpolateElement,
10
+ } from '@wordpress/element';
5
11
  import {
6
12
  __experimentalSpacer as Spacer,
7
13
  __experimentalInputControl as InputControl,
8
14
  __experimentalText as Text,
15
+ __experimentalHStack as HStack,
9
16
  SelectControl,
10
17
  Spinner,
11
18
  Icon,
@@ -14,7 +21,7 @@ import {
14
21
  Button,
15
22
  } from '@wordpress/components';
16
23
  import { debounce } from '@wordpress/compose';
17
- import { __, _x } from '@wordpress/i18n';
24
+ import { sprintf, __, _x } from '@wordpress/i18n';
18
25
  import { search, closeSmall } from '@wordpress/icons';
19
26
 
20
27
  /**
@@ -22,7 +29,6 @@ import { search, closeSmall } from '@wordpress/icons';
22
29
  */
23
30
  import TabPanelLayout from './tab-panel-layout';
24
31
  import { FontLibraryContext } from './context';
25
- import FontsGrid from './fonts-grid';
26
32
  import FontCard from './font-card';
27
33
  import filterFonts from './utils/filter-fonts';
28
34
  import CollectionFontDetails from './collection-font-details';
@@ -48,6 +54,7 @@ function FontCollection( { slug } ) {
48
54
 
49
55
  const [ selectedFont, setSelectedFont ] = useState( null );
50
56
  const [ fontsToInstall, setFontsToInstall ] = useState( [] );
57
+ const [ page, setPage ] = useState( 1 );
51
58
  const [ filters, setFilters ] = useState( {} );
52
59
  const [ renderConfirmDialog, setRenderConfirmDialog ] = useState(
53
60
  requiresPermission && ! getGoogleFontsPermissionFromStorage()
@@ -109,22 +116,34 @@ function FontCollection( { slug } ) {
109
116
  [ collectionFonts, filters ]
110
117
  );
111
118
 
119
+ // NOTE: The height of the font library modal unavailable to use for rendering font family items is roughly 417px
120
+ // The height of each font family item is 61px.
121
+ const pageSize = Math.floor( ( window.innerHeight - 417 ) / 61 );
122
+ const totalPages = Math.ceil( fonts.length / pageSize );
123
+ const itemsStart = ( page - 1 ) * pageSize;
124
+ const itemsLimit = page * pageSize;
125
+ const items = fonts.slice( itemsStart, itemsLimit );
126
+
112
127
  const handleCategoryFilter = ( category ) => {
113
128
  setFilters( { ...filters, category } );
129
+ setPage( 1 );
114
130
  };
115
131
 
116
132
  const handleUpdateSearchInput = ( value ) => {
117
133
  setFilters( { ...filters, search: value } );
134
+ setPage( 1 );
118
135
  };
119
136
 
120
137
  const debouncedUpdateSearchInput = debounce( handleUpdateSearchInput, 300 );
121
138
 
122
139
  const resetFilters = () => {
123
140
  setFilters( {} );
141
+ setPage( 1 );
124
142
  };
125
143
 
126
144
  const resetSearch = () => {
127
145
  setFilters( { ...filters, search: '' } );
146
+ setPage( 1 );
128
147
  };
129
148
 
130
149
  const handleUnselectFont = () => {
@@ -186,6 +205,24 @@ function FontCollection( { slug } ) {
186
205
  resetFontsToInstall();
187
206
  };
188
207
 
208
+ let footerComponent = null;
209
+ if ( selectedFont ) {
210
+ footerComponent = (
211
+ <InstallFooter
212
+ handleInstall={ handleInstall }
213
+ isDisabled={ fontsToInstall.length === 0 }
214
+ />
215
+ );
216
+ } else if ( ! renderConfirmDialog && totalPages > 1 ) {
217
+ footerComponent = (
218
+ <PaginationFooter
219
+ page={ page }
220
+ totalPages={ totalPages }
221
+ setPage={ setPage }
222
+ />
223
+ );
224
+ }
225
+
189
226
  return (
190
227
  <TabPanelLayout
191
228
  title={
@@ -198,12 +235,7 @@ function FontCollection( { slug } ) {
198
235
  }
199
236
  notice={ notice }
200
237
  handleBack={ !! selectedFont && handleUnselectFont }
201
- footer={
202
- <Footer
203
- handleInstall={ handleInstall }
204
- isDisabled={ fontsToInstall.length === 0 }
205
- />
206
- }
238
+ footer={ footerComponent }
207
239
  >
208
240
  { renderConfirmDialog && (
209
241
  <>
@@ -275,8 +307,8 @@ function FontCollection( { slug } ) {
275
307
  ) }
276
308
 
277
309
  { ! renderConfirmDialog && ! selectedFont && (
278
- <FontsGrid>
279
- { fonts.map( ( font ) => (
310
+ <div className="font-library-modal__fonts-grid__main">
311
+ { items.map( ( font ) => (
280
312
  <FontCard
281
313
  key={ font.font_family_settings.slug }
282
314
  font={ font.font_family_settings }
@@ -285,13 +317,86 @@ function FontCollection( { slug } ) {
285
317
  } }
286
318
  />
287
319
  ) ) }
288
- </FontsGrid>
320
+ </div>
289
321
  ) }
290
322
  </TabPanelLayout>
291
323
  );
292
324
  }
293
325
 
294
- function Footer( { handleInstall, isDisabled } ) {
326
+ function PaginationFooter( { page, totalPages, setPage } ) {
327
+ return (
328
+ <Flex justify="center">
329
+ <Button
330
+ label={ __( 'First page' ) }
331
+ size="compact"
332
+ onClick={ () => setPage( 1 ) }
333
+ disabled={ page === 1 }
334
+ __experimentalIsFocusable
335
+ >
336
+ <span>«</span>
337
+ </Button>
338
+ <Button
339
+ label={ __( 'Previous page' ) }
340
+ size="compact"
341
+ onClick={ () => setPage( page - 1 ) }
342
+ disabled={ page === 1 }
343
+ __experimentalIsFocusable
344
+ >
345
+ <span>‹</span>
346
+ </Button>
347
+ <HStack justify="flex-start" expanded={ false } spacing={ 2 }>
348
+ { createInterpolateElement(
349
+ sprintf(
350
+ // translators: %s: Total number of pages.
351
+ _x( 'Page <CurrenPageControl /> of %s', 'paging' ),
352
+ totalPages
353
+ ),
354
+ {
355
+ CurrenPageControl: (
356
+ <SelectControl
357
+ aria-label={ __( 'Current page' ) }
358
+ value={ page }
359
+ options={ [ ...Array( totalPages ) ].map(
360
+ ( e, i ) => {
361
+ return {
362
+ label: i + 1,
363
+ value: i + 1,
364
+ };
365
+ }
366
+ ) }
367
+ onChange={ ( newPage ) =>
368
+ setPage( parseInt( newPage ) )
369
+ }
370
+ size={ 'compact' }
371
+ __nextHasNoMarginBottom
372
+ />
373
+ ),
374
+ }
375
+ ) }
376
+ </HStack>
377
+ <Button
378
+ label={ __( 'Next page' ) }
379
+ size="compact"
380
+ onClick={ () => setPage( page + 1 ) }
381
+ disabled={ page === totalPages }
382
+ __experimentalIsFocusable
383
+ >
384
+ <span>›</span>
385
+ </Button>
386
+ <Button
387
+ label={ __( 'Last page' ) }
388
+ size="compact"
389
+ onClick={ () => setPage( totalPages ) }
390
+ disabled={ page === totalPages }
391
+ __experimentalIsFocusable
392
+ >
393
+ <span>»</span>
394
+ </Button>
395
+ </Flex>
396
+ );
397
+ }
398
+
399
+ function InstallFooter( { handleInstall, isDisabled } ) {
295
400
  const { isInstalling } = useContext( FontLibraryContext );
296
401
 
297
402
  return (
@@ -24,7 +24,7 @@ function GoogleFontsConfirmDialog() {
24
24
  <div className="font-library__google-fonts-confirm">
25
25
  <Card>
26
26
  <CardBody>
27
- <Text as="h3">Connect to Google Fonts</Text>
27
+ <Text as="h3">{ __( 'Connect to Google Fonts' ) }</Text>
28
28
  <Spacer margin={ 6 } />
29
29
  <Text as="p">
30
30
  { __(
@@ -7,6 +7,7 @@ import {
7
7
  privateApis as componentsPrivateApis,
8
8
  __experimentalHStack as HStack,
9
9
  __experimentalSpacer as Spacer,
10
+ __experimentalText as Text,
10
11
  Button,
11
12
  Spinner,
12
13
  FlexItem,
@@ -17,7 +18,6 @@ import {
17
18
  */
18
19
  import TabPanelLayout from './tab-panel-layout';
19
20
  import { FontLibraryContext } from './context';
20
- import FontsGrid from './fonts-grid';
21
21
  import LibraryFontDetails from './library-font-details';
22
22
  import LibraryFontCard from './library-font-card';
23
23
  import ConfirmDeleteDialog from './confirm-delete-dialog';
@@ -123,36 +123,38 @@ function InstalledFonts() {
123
123
  ) }
124
124
  { baseCustomFonts.length > 0 && (
125
125
  <>
126
- <FontsGrid>
127
- { baseCustomFonts.map( ( font ) => (
128
- <LibraryFontCard
129
- font={ font }
130
- key={ font.slug }
131
- onClick={ () => {
132
- handleSelectFont( font );
133
- } }
134
- />
135
- ) ) }
136
- </FontsGrid>
126
+ { baseCustomFonts.map( ( font ) => (
127
+ <LibraryFontCard
128
+ font={ font }
129
+ key={ font.slug }
130
+ onClick={ () => {
131
+ handleSelectFont( font );
132
+ } }
133
+ />
134
+ ) ) }
137
135
  <Spacer margin={ 8 } />
138
136
  </>
139
137
  ) }
140
138
 
141
139
  { baseThemeFonts.length > 0 && (
142
140
  <>
143
- <FontsGrid title={ __( 'Theme Fonts' ) }>
144
- { baseThemeFonts.map( ( font ) => (
145
- <LibraryFontCard
146
- font={ font }
147
- key={ font.slug }
148
- onClick={ () => {
149
- handleSelectFont( font );
150
- } }
151
- />
152
- ) ) }
153
- </FontsGrid>
141
+ <Text className="font-library-modal__subtitle">
142
+ { __( 'Theme Fonts' ) }
143
+ </Text>
144
+
145
+ <Spacer margin={ 2 } />
146
+ { baseThemeFonts.map( ( font ) => (
147
+ <LibraryFontCard
148
+ font={ font }
149
+ key={ font.slug }
150
+ onClick={ () => {
151
+ handleSelectFont( font );
152
+ } }
153
+ />
154
+ ) ) }
154
155
  </>
155
156
  ) }
157
+ <Spacer margin={ 16 } />
156
158
  </>
157
159
  ) }
158
160
 
@@ -36,11 +36,8 @@
36
36
  }
37
37
  }
38
38
 
39
- .font-library-modal__fonts-grid {
40
- .font-library-modal__fonts-grid__main {
41
- display: flex;
42
- flex-direction: column;
43
- }
39
+ .font-library-modal__tabpanel-layout .components-base-control__field {
40
+ margin-bottom: 0;
44
41
  }
45
42
 
46
43
  .font-library-modal__font-card {
@@ -9,7 +9,7 @@ import { privateApis as componentsPrivateApis } from '@wordpress/components';
9
9
  import { FONT_WEIGHTS, FONT_STYLES } from './constants';
10
10
  import { unlock } from '../../../../lock-unlock';
11
11
  import { fetchInstallFontFace } from '../resolvers';
12
- import { formatFontFamily } from './preview-styles';
12
+ import { formatFontFaceName } from './preview-styles';
13
13
 
14
14
  /**
15
15
  * Browser dependencies
@@ -99,7 +99,7 @@ export async function loadFontFaceInBrowser( fontFace, source, addTo = 'all' ) {
99
99
  }
100
100
 
101
101
  const newFont = new window.FontFace(
102
- formatFontFamily( fontFace.fontFamily ),
102
+ formatFontFaceName( fontFace.fontFamily ),
103
103
  dataSource,
104
104
  {
105
105
  style: fontFace.fontStyle,
@@ -1,10 +1,22 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { privateApis as componentsPrivateApis } from '@wordpress/components';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import { unlock } from '../../../../lock-unlock';
10
+
11
+ const { kebabCase } = unlock( componentsPrivateApis );
12
+
1
13
  export default function makeFamiliesFromFaces( fontFaces ) {
2
14
  const fontFamiliesObject = fontFaces.reduce( ( acc, item ) => {
3
15
  if ( ! acc[ item.fontFamily ] ) {
4
16
  acc[ item.fontFamily ] = {
5
17
  name: item.fontFamily,
6
18
  fontFamily: item.fontFamily,
7
- slug: item.fontFamily.replace( /\s+/g, '-' ).toLowerCase(),
19
+ slug: kebabCase( item.fontFamily.toLowerCase() ),
8
20
  fontFace: [],
9
21
  };
10
22
  }
@@ -30,22 +30,79 @@ function extractFontWeights( fontFaces ) {
30
30
  return result;
31
31
  }
32
32
 
33
+ /*
34
+ * Format the font family to use in the CSS font-family property of a CSS rule.
35
+ *
36
+ * The input can be a string with the font family name or a string with multiple font family names separated by commas.
37
+ * It follows the recommendations from the CSS Fonts Module Level 4.
38
+ * https://www.w3.org/TR/css-fonts-4/#font-family-prop
39
+ *
40
+ * @param {string} input - The font family.
41
+ * @return {string} The formatted font family.
42
+ *
43
+ * Example:
44
+ * formatFontFamily( "Open Sans, Font+Name, sans-serif" ) => '"Open Sans", "Font+Name", sans-serif'
45
+ * formatFontFamily( "'Open Sans', generic(kai), sans-serif" ) => '"Open Sans", sans-serif'
46
+ * formatFontFamily( "DotGothic16, Slabo 27px, serif" ) => '"DotGothic16","Slabo 27px",serif'
47
+ * formatFontFamily( "Mine's, Moe's Typography" ) => `"mine's","Moe's Typography"`
48
+ */
33
49
  export function formatFontFamily( input ) {
34
- return input
35
- .split( ',' )
36
- .map( ( font ) => {
37
- font = font.trim(); // Remove any leading or trailing white spaces
38
- // If the font doesn't start with quotes and contains a space, then wrap in quotes.
39
- // Check that string starts with a single or double quote and not a space
40
- if (
41
- ! ( font.startsWith( '"' ) || font.startsWith( "'" ) ) &&
42
- font.indexOf( ' ' ) !== -1
43
- ) {
44
- return `"${ font }"`;
45
- }
46
- return font; // Return font as is if no transformation is needed
47
- } )
48
- .join( ', ' );
50
+ // Matches strings that are not exclusively alphabetic characters or hyphens, and do not exactly follow the pattern generic(alphabetic characters or hyphens).
51
+ const regex = /^(?!generic\([ a-zA-Z\-]+\)$)(?!^[a-zA-Z\-]+$).+/;
52
+ const output = input.trim();
53
+
54
+ const formatItem = ( item ) => {
55
+ item = item.trim();
56
+ if ( item.match( regex ) ) {
57
+ // removes leading and trailing quotes.
58
+ item = item.replace( /^["']|["']$/g, '' );
59
+ return `"${ item }"`;
60
+ }
61
+ return item;
62
+ };
63
+
64
+ if ( output.includes( ',' ) ) {
65
+ return output
66
+ .split( ',' )
67
+ .map( formatItem )
68
+ .filter( ( item ) => item !== '' )
69
+ .join( ', ' );
70
+ }
71
+
72
+ return formatItem( output );
73
+ }
74
+
75
+ /*
76
+ * Format the font face name to use in the font-family property of a font face.
77
+ *
78
+ * The input can be a string with the font face name or a string with multiple font face names separated by commas.
79
+ * It removes the leading and trailing quotes from the font face name.
80
+ *
81
+ * @param {string} input - The font face name.
82
+ * @return {string} The formatted font face name.
83
+ *
84
+ * Example:
85
+ * formatFontFaceName("Open Sans") => "Open Sans"
86
+ * formatFontFaceName("'Open Sans', sans-serif") => "Open Sans"
87
+ * formatFontFaceName(", 'Open Sans', 'Helvetica Neue', sans-serif") => "Open Sans"
88
+ */
89
+ export function formatFontFaceName( input ) {
90
+ let output = input.trim();
91
+ if ( output.includes( ',' ) ) {
92
+ output = output
93
+ .split( ',' )
94
+ // finds the first item that is not an empty string.
95
+ .find( ( item ) => item.trim() !== '' )
96
+ .trim();
97
+ }
98
+ // removes leading and trailing quotes.
99
+ output = output.replace( /^["']|["']$/g, '' );
100
+
101
+ // Firefox needs the font name to be wrapped in double quotes meanwhile other browsers don't.
102
+ if ( window.navigator.userAgent.toLowerCase().includes( 'firefox' ) ) {
103
+ output = `"${ output }"`;
104
+ }
105
+ return output;
49
106
  }
50
107
 
51
108
  export function getFamilyPreviewStyle( family ) {
@@ -1,7 +1,11 @@
1
1
  /**
2
2
  * Internal dependencies
3
3
  */
4
- import { getFamilyPreviewStyle, formatFontFamily } from '../preview-styles';
4
+ import {
5
+ getFamilyPreviewStyle,
6
+ formatFontFamily,
7
+ formatFontFaceName,
8
+ } from '../preview-styles';
5
9
 
6
10
  describe( 'getFamilyPreviewStyle', () => {
7
11
  it( 'should return default fontStyle and fontWeight if fontFace is not provided', () => {
@@ -139,7 +143,7 @@ describe( 'formatFontFamily', () => {
139
143
  "Seravek, 'Gill Sans Nova', Ubuntu, Calibri, 'DejaVu Sans', source-sans-pro, sans-serif"
140
144
  )
141
145
  ).toBe(
142
- "Seravek, 'Gill Sans Nova', Ubuntu, Calibri, 'DejaVu Sans', source-sans-pro, sans-serif"
146
+ 'Seravek, "Gill Sans Nova", Ubuntu, Calibri, "DejaVu Sans", source-sans-pro, sans-serif'
143
147
  );
144
148
  } );
145
149
 
@@ -153,9 +157,60 @@ describe( 'formatFontFamily', () => {
153
157
  );
154
158
  } );
155
159
 
156
- it( 'should wrap only those font names with spaces which are not already quoted', () => {
157
- expect( formatFontFamily( 'Baloo Bhai 2, Arial' ) ).toBe(
158
- '"Baloo Bhai 2", Arial'
160
+ it( 'should wrap names with special characters in quotes', () => {
161
+ expect(
162
+ formatFontFamily(
163
+ 'Font+Name, Font*Name, _Font_Name_, generic(kai), sans-serif'
164
+ )
165
+ ).toBe(
166
+ '"Font+Name", "Font*Name", "_Font_Name_", generic(kai), sans-serif'
167
+ );
168
+ } );
169
+
170
+ it( 'should fix empty wrong formatted font family', () => {
171
+ expect( formatFontFamily( ', Abril Fatface,Times,serif' ) ).toBe(
172
+ '"Abril Fatface", Times, serif'
173
+ );
174
+ } );
175
+
176
+ it( 'should not add quotes to generic names', () => {
177
+ expect(
178
+ formatFontFamily(
179
+ 'Paren(thesis)Font, generic(kai), generic(fasongsong), generic( abc ), Helvetica Neue'
180
+ )
181
+ ).toBe(
182
+ '"Paren(thesis)Font", generic(kai), generic(fasongsong), generic( abc ), "Helvetica Neue"'
159
183
  );
160
184
  } );
161
185
  } );
186
+
187
+ describe( 'formatFontFaceName', () => {
188
+ it( 'should remove leading and trailing quotes', () => {
189
+ expect( formatFontFaceName( '"Open Sans"' ) ).toBe( 'Open Sans' );
190
+ } );
191
+
192
+ it( 'should remove leading and trailing quotes from multiple font face names', () => {
193
+ expect(
194
+ formatFontFaceName( "'Open Sans', 'Helvetica Neue', sans-serif" )
195
+ ).toBe( 'Open Sans' );
196
+ } );
197
+
198
+ it( 'should remove leading and trailing quotes even from names with spaces and special characters', () => {
199
+ expect( formatFontFaceName( "'Font+Name 24', sans-serif" ) ).toBe(
200
+ 'Font+Name 24'
201
+ );
202
+ } );
203
+
204
+ it( 'should ouput the font face name with quotes on Firefox', () => {
205
+ const mockUserAgent =
206
+ 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:122.0) Gecko/20100101 Firefox/122.0';
207
+
208
+ // Mock the userAgent for this test
209
+ Object.defineProperty( window.navigator, 'userAgent', {
210
+ value: mockUserAgent,
211
+ configurable: true,
212
+ } );
213
+
214
+ expect( formatFontFaceName( 'Open Sans' ) ).toBe( '"Open Sans"' );
215
+ } );
216
+ } );