@wordpress/block-library 9.26.0 → 9.27.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 (174) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/button/edit.js +1 -1
  3. package/build/button/edit.js.map +1 -1
  4. package/build/cover/edit/block-controls.js +4 -2
  5. package/build/cover/edit/block-controls.js.map +1 -1
  6. package/build/cover/edit/index.js +6 -3
  7. package/build/cover/edit/index.js.map +1 -1
  8. package/build/cover/edit/inspector-controls.js +11 -4
  9. package/build/cover/edit/inspector-controls.js.map +1 -1
  10. package/build/cover/edit/poster-image.js +81 -0
  11. package/build/cover/edit/poster-image.js.map +1 -0
  12. package/build/cover/index.js +6 -0
  13. package/build/cover/index.js.map +1 -1
  14. package/build/cover/save.js +3 -1
  15. package/build/cover/save.js.map +1 -1
  16. package/build/details/index.js +1 -1
  17. package/build/details/index.js.map +1 -1
  18. package/build/gallery/constants.js +2 -1
  19. package/build/gallery/constants.js.map +1 -1
  20. package/build/gallery/edit.js +93 -15
  21. package/build/gallery/edit.js.map +1 -1
  22. package/build/image/edit.js +6 -0
  23. package/build/image/edit.js.map +1 -1
  24. package/build/list/index.js +0 -1
  25. package/build/list/index.js.map +1 -1
  26. package/build/media-text/edit.js +2 -2
  27. package/build/media-text/edit.js.map +1 -1
  28. package/build/more/edit.native.js +17 -32
  29. package/build/more/edit.native.js.map +1 -1
  30. package/build/navigation-link/edit.js +49 -16
  31. package/build/navigation-link/edit.js.map +1 -1
  32. package/build/navigation-link/update-attributes.js +112 -14
  33. package/build/navigation-link/update-attributes.js.map +1 -1
  34. package/build/navigation-submenu/edit.js +19 -2
  35. package/build/navigation-submenu/edit.js.map +1 -1
  36. package/build/post-author/edit.js +78 -35
  37. package/build/post-author/edit.js.map +1 -1
  38. package/build/post-comments-form/form.js +1 -1
  39. package/build/post-comments-form/form.js.map +1 -1
  40. package/build/post-content/edit.js +78 -16
  41. package/build/post-content/edit.js.map +1 -1
  42. package/build/post-content/index.js +6 -0
  43. package/build/post-content/index.js.map +1 -1
  44. package/build/post-featured-image/edit.js +2 -1
  45. package/build/post-featured-image/edit.js.map +1 -1
  46. package/build/search/edit.js +1 -1
  47. package/build/search/edit.js.map +1 -1
  48. package/build/separator/edit.js +5 -30
  49. package/build/separator/edit.js.map +1 -1
  50. package/build/site-logo/edit.js +16 -5
  51. package/build/site-logo/edit.js.map +1 -1
  52. package/build/site-tagline/index.js +1 -1
  53. package/build/video/edit.js +2 -5
  54. package/build/video/edit.js.map +1 -1
  55. package/build/video/poster-image.js +25 -25
  56. package/build/video/poster-image.js.map +1 -1
  57. package/build/video/tracks-editor.js +95 -104
  58. package/build/video/tracks-editor.js.map +1 -1
  59. package/build/video/tracks.js +6 -2
  60. package/build/video/tracks.js.map +1 -1
  61. package/build-module/button/edit.js +1 -1
  62. package/build-module/button/edit.js.map +1 -1
  63. package/build-module/cover/edit/block-controls.js +4 -2
  64. package/build-module/cover/edit/block-controls.js.map +1 -1
  65. package/build-module/cover/edit/index.js +6 -3
  66. package/build-module/cover/edit/index.js.map +1 -1
  67. package/build-module/cover/edit/inspector-controls.js +10 -4
  68. package/build-module/cover/edit/inspector-controls.js.map +1 -1
  69. package/build-module/cover/edit/poster-image.js +74 -0
  70. package/build-module/cover/edit/poster-image.js.map +1 -0
  71. package/build-module/cover/index.js +6 -0
  72. package/build-module/cover/index.js.map +1 -1
  73. package/build-module/cover/save.js +3 -1
  74. package/build-module/cover/save.js.map +1 -1
  75. package/build-module/details/index.js +1 -1
  76. package/build-module/details/index.js.map +1 -1
  77. package/build-module/gallery/constants.js +1 -0
  78. package/build-module/gallery/constants.js.map +1 -1
  79. package/build-module/gallery/edit.js +95 -17
  80. package/build-module/gallery/edit.js.map +1 -1
  81. package/build-module/image/edit.js +6 -0
  82. package/build-module/image/edit.js.map +1 -1
  83. package/build-module/list/index.js +0 -1
  84. package/build-module/list/index.js.map +1 -1
  85. package/build-module/media-text/edit.js +2 -2
  86. package/build-module/media-text/edit.js.map +1 -1
  87. package/build-module/more/edit.native.js +16 -30
  88. package/build-module/more/edit.native.js.map +1 -1
  89. package/build-module/navigation-link/edit.js +50 -17
  90. package/build-module/navigation-link/edit.js.map +1 -1
  91. package/build-module/navigation-link/update-attributes.js +113 -15
  92. package/build-module/navigation-link/update-attributes.js.map +1 -1
  93. package/build-module/navigation-submenu/edit.js +20 -3
  94. package/build-module/navigation-submenu/edit.js.map +1 -1
  95. package/build-module/post-author/edit.js +78 -35
  96. package/build-module/post-author/edit.js.map +1 -1
  97. package/build-module/post-comments-form/form.js +1 -1
  98. package/build-module/post-comments-form/form.js.map +1 -1
  99. package/build-module/post-content/edit.js +80 -18
  100. package/build-module/post-content/edit.js.map +1 -1
  101. package/build-module/post-content/index.js +6 -0
  102. package/build-module/post-content/index.js.map +1 -1
  103. package/build-module/post-featured-image/edit.js +2 -1
  104. package/build-module/post-featured-image/edit.js.map +1 -1
  105. package/build-module/search/edit.js +1 -1
  106. package/build-module/search/edit.js.map +1 -1
  107. package/build-module/separator/edit.js +6 -31
  108. package/build-module/separator/edit.js.map +1 -1
  109. package/build-module/site-logo/edit.js +17 -6
  110. package/build-module/site-logo/edit.js.map +1 -1
  111. package/build-module/site-tagline/index.js +1 -1
  112. package/build-module/video/edit.js +2 -5
  113. package/build-module/video/edit.js.map +1 -1
  114. package/build-module/video/poster-image.js +26 -26
  115. package/build-module/video/poster-image.js.map +1 -1
  116. package/build-module/video/tracks-editor.js +96 -105
  117. package/build-module/video/tracks-editor.js.map +1 -1
  118. package/build-module/video/tracks.js +6 -2
  119. package/build-module/video/tracks.js.map +1 -1
  120. package/build-style/archives/editor-rtl.css +0 -4
  121. package/build-style/archives/editor.css +0 -4
  122. package/build-style/editor-rtl.css +0 -21
  123. package/build-style/editor.css +0 -21
  124. package/build-style/file/style-rtl.css +1 -1
  125. package/build-style/file/style.css +1 -1
  126. package/build-style/gallery/editor-rtl.css +0 -13
  127. package/build-style/gallery/editor.css +0 -13
  128. package/build-style/navigation/style-rtl.css +2 -0
  129. package/build-style/navigation/style.css +2 -0
  130. package/build-style/style-rtl.css +3 -1
  131. package/build-style/style.css +3 -1
  132. package/build-style/video/editor-rtl.css +0 -4
  133. package/build-style/video/editor.css +0 -4
  134. package/package.json +35 -35
  135. package/src/archives/editor.scss +0 -4
  136. package/src/button/edit.js +1 -1
  137. package/src/comments-pagination/index.php +7 -2
  138. package/src/cover/block.json +6 -0
  139. package/src/cover/edit/block-controls.js +22 -17
  140. package/src/cover/edit/index.js +4 -1
  141. package/src/cover/edit/inspector-controls.js +12 -3
  142. package/src/cover/edit/poster-image.js +91 -0
  143. package/src/cover/save.js +2 -0
  144. package/src/details/index.js +1 -1
  145. package/src/file/style.scss +1 -1
  146. package/src/gallery/constants.js +1 -0
  147. package/src/gallery/edit.js +182 -68
  148. package/src/gallery/editor.scss +0 -17
  149. package/src/image/edit.js +12 -0
  150. package/src/list/block.json +0 -1
  151. package/src/media-text/edit.js +1 -1
  152. package/src/more/edit.native.js +19 -33
  153. package/src/navigation/style.scss +2 -0
  154. package/src/navigation-link/edit.js +46 -17
  155. package/src/navigation-link/test/edit.js +738 -6
  156. package/src/navigation-link/update-attributes.js +125 -12
  157. package/src/navigation-submenu/edit.js +21 -1
  158. package/src/post-author/edit.js +91 -40
  159. package/src/post-comments-form/form.js +1 -1
  160. package/src/post-content/block.json +6 -0
  161. package/src/post-content/edit.js +71 -19
  162. package/src/post-content/index.php +11 -4
  163. package/src/post-featured-image/edit.js +1 -0
  164. package/src/post-featured-image/index.php +3 -2
  165. package/src/rss/index.php +2 -1
  166. package/src/search/edit.js +1 -1
  167. package/src/separator/edit.js +8 -43
  168. package/src/site-logo/edit.js +22 -10
  169. package/src/site-tagline/block.json +1 -1
  170. package/src/video/edit.js +1 -4
  171. package/src/video/editor.scss +0 -6
  172. package/src/video/poster-image.js +29 -24
  173. package/src/video/tracks-editor.js +93 -103
  174. package/src/video/tracks.js +2 -1
@@ -7,26 +7,18 @@ import clsx from 'clsx';
7
7
  * WordPress dependencies
8
8
  */
9
9
  import {
10
- HorizontalRule,
11
- SelectControl,
12
- PanelBody,
13
- __experimentalToolsPanel as ToolsPanel,
14
- __experimentalToolsPanelItem as ToolsPanelItem,
15
- } from '@wordpress/components';
16
- import {
17
- useBlockProps,
18
10
  getColorClassName,
19
- __experimentalUseColorProps as useColorProps,
20
11
  InspectorControls,
12
+ useBlockProps,
13
+ __experimentalUseColorProps as useColorProps,
21
14
  } from '@wordpress/block-editor';
22
- import { Platform } from '@wordpress/element';
15
+ import { HorizontalRule, SelectControl } from '@wordpress/components';
23
16
  import { __ } from '@wordpress/i18n';
24
17
 
25
18
  /**
26
19
  * Internal dependencies
27
20
  */
28
21
  import useDeprecatedOpacity from './use-deprecated-opacity';
29
- import { useToolsPanelDropdownMenuProps } from '../utils/hooks';
30
22
 
31
23
  const HtmlElementControl = ( { tagName, setAttributes } ) => {
32
24
  return (
@@ -58,7 +50,6 @@ export default function SeparatorEdit( { attributes, setAttributes } ) {
58
50
  const colorProps = useColorProps( attributes );
59
51
  const currentColor = colorProps?.style?.backgroundColor;
60
52
  const hasCustomColor = !! style?.color?.background;
61
- const dropdownMenuProps = useToolsPanelDropdownMenuProps();
62
53
 
63
54
  useDeprecatedOpacity( opacity, currentColor, setAttributes );
64
55
 
@@ -84,37 +75,11 @@ export default function SeparatorEdit( { attributes, setAttributes } ) {
84
75
 
85
76
  return (
86
77
  <>
87
- <InspectorControls>
88
- { Platform.isNative ? (
89
- <PanelBody title={ __( 'Settings' ) }>
90
- <HtmlElementControl
91
- tagName={ tagName }
92
- setAttributes={ setAttributes }
93
- />
94
- </PanelBody>
95
- ) : (
96
- <ToolsPanel
97
- label={ __( 'Settings' ) }
98
- resetAll={ () => {
99
- setAttributes( { tagName: 'hr' } );
100
- } }
101
- dropdownMenuProps={ dropdownMenuProps }
102
- >
103
- <ToolsPanelItem
104
- hasValue={ () => tagName !== 'hr' }
105
- label={ __( 'HTML element' ) }
106
- onDeselect={ () =>
107
- setAttributes( { tagName: 'hr' } )
108
- }
109
- isShownByDefault
110
- >
111
- <HtmlElementControl
112
- tagName={ tagName }
113
- setAttributes={ setAttributes }
114
- />
115
- </ToolsPanelItem>
116
- </ToolsPanel>
117
- ) }
78
+ <InspectorControls group="advanced">
79
+ <HtmlElementControl
80
+ tagName={ tagName }
81
+ setAttributes={ setAttributes }
82
+ />
118
83
  </InspectorControls>
119
84
  <Wrapper
120
85
  { ...useBlockProps( {
@@ -23,7 +23,6 @@ import {
23
23
  Button,
24
24
  DropZone,
25
25
  FlexItem,
26
- PanelBody,
27
26
  __experimentalToolsPanel as ToolsPanel,
28
27
  __experimentalToolsPanelItem as ToolsPanelItem,
29
28
  __experimentalItemGroup as ItemGroup,
@@ -476,6 +475,7 @@ export default function LogoEdit( {
476
475
  }, [] );
477
476
  const { getSettings } = useSelect( blockEditorStore );
478
477
  const [ temporaryURL, setTemporaryURL ] = useState();
478
+ const dropdownMenuProps = useToolsPanelDropdownMenuProps();
479
479
 
480
480
  const { editEntityRecord } = useDispatch( coreStore );
481
481
 
@@ -633,9 +633,15 @@ export default function LogoEdit( {
633
633
 
634
634
  const mediaInspectorPanel = ( canUserEdit || logoUrl ) && (
635
635
  <InspectorControls>
636
- <PanelBody title={ __( 'Media' ) }>
637
- <div className="block-library-site-logo__inspector-media-replace-container">
638
- { ! canUserEdit ? (
636
+ <ToolsPanel
637
+ label={ __( 'Media' ) }
638
+ dropdownMenuProps={ dropdownMenuProps }
639
+ >
640
+ { ! canUserEdit ? (
641
+ <div
642
+ className="block-library-site-logo__inspector-media-replace-container"
643
+ style={ { gridColumn: '1 / -1' } }
644
+ >
639
645
  <InspectorLogoPreview
640
646
  media={ mediaItemData }
641
647
  itemGroupProps={ {
@@ -644,8 +650,14 @@ export default function LogoEdit( {
644
650
  'block-library-site-logo__inspector-readonly-logo-preview',
645
651
  } }
646
652
  />
647
- ) : (
648
- <>
653
+ </div>
654
+ ) : (
655
+ <ToolsPanelItem
656
+ hasValue={ () => !! logoUrl }
657
+ label={ __( 'Logo' ) }
658
+ isShownByDefault
659
+ >
660
+ <div className="block-library-site-logo__inspector-media-replace-container">
649
661
  <SiteLogoReplaceFlow
650
662
  { ...mediaReplaceFlowProps }
651
663
  name={
@@ -668,10 +680,10 @@ export default function LogoEdit( {
668
680
  ) }
669
681
  />
670
682
  <DropZone onFilesDrop={ onFilesDrop } />
671
- </>
672
- ) }
673
- </div>
674
- </PanelBody>
683
+ </div>
684
+ </ToolsPanelItem>
685
+ ) }
686
+ </ToolsPanel>
675
687
  </InspectorControls>
676
688
  );
677
689
 
@@ -4,7 +4,7 @@
4
4
  "name": "core/site-tagline",
5
5
  "title": "Site Tagline",
6
6
  "category": "theme",
7
- "description": "Describe in a few words what the site is about. The tagline can be used in search results or when sharing on social networks even if it’s not displayed in the theme design.",
7
+ "description": "Describe in a few words what this site is about. This is important for search results, sharing on social media, and gives overall clarity to visitors.",
8
8
  "keywords": [ "description" ],
9
9
  "textdomain": "default",
10
10
  "attributes": {
package/src/video/edit.js CHANGED
@@ -23,7 +23,6 @@ import {
23
23
  } from '@wordpress/block-editor';
24
24
  import { useRef, useEffect, useState } from '@wordpress/element';
25
25
  import { __ } from '@wordpress/i18n';
26
- import { useInstanceId } from '@wordpress/compose';
27
26
  import { useDispatch } from '@wordpress/data';
28
27
  import { video as icon } from '@wordpress/icons';
29
28
  import { store as noticesStore } from '@wordpress/notices';
@@ -52,7 +51,6 @@ function VideoEdit( {
52
51
  insertBlocksAfter,
53
52
  onReplace,
54
53
  } ) {
55
- const instanceId = useInstanceId( VideoEdit );
56
54
  const videoPlayer = useRef();
57
55
  const { id, controls, poster, src, tracks } = attributes;
58
56
  const [ temporaryURL, setTemporaryURL ] = useState( attributes.blob );
@@ -210,7 +208,7 @@ function VideoEdit( {
210
208
  muted: false,
211
209
  playsInline: false,
212
210
  preload: 'metadata',
213
- poster: '',
211
+ poster: undefined,
214
212
  } );
215
213
  } }
216
214
  dropdownMenuProps={ dropdownMenuProps }
@@ -222,7 +220,6 @@ function VideoEdit( {
222
220
  <PosterImage
223
221
  poster={ poster }
224
222
  setAttributes={ setAttributes }
225
- instanceId={ instanceId }
226
223
  />
227
224
  </ToolsPanel>
228
225
  </InspectorControls>
@@ -19,12 +19,6 @@
19
19
  }
20
20
  }
21
21
 
22
- .editor-video-poster-control {
23
- .components-button {
24
- margin-right: $grid-unit-10;
25
- }
26
- }
27
-
28
22
  .block-library-video-tracks-editor {
29
23
  z-index: z-index("{core/video track editor popover}");
30
24
  }
@@ -5,16 +5,21 @@ import { MediaUpload, MediaUploadCheck } from '@wordpress/block-editor';
5
5
  import {
6
6
  Button,
7
7
  BaseControl,
8
+ __experimentalHStack as HStack,
8
9
  __experimentalToolsPanelItem as ToolsPanelItem,
9
10
  } from '@wordpress/components';
10
11
  import { __, sprintf } from '@wordpress/i18n';
11
12
  import { useRef } from '@wordpress/element';
13
+ import { useInstanceId } from '@wordpress/compose';
12
14
 
13
- function PosterImage( { poster, setAttributes, instanceId } ) {
14
- const posterImageButton = useRef();
15
- const VIDEO_POSTER_ALLOWED_MEDIA_TYPES = [ 'image' ];
15
+ const VIDEO_POSTER_ALLOWED_MEDIA_TYPES = [ 'image' ];
16
16
 
17
- const videoPosterDescription = `video-block__poster-image-description-${ instanceId }`;
17
+ function PosterImage( { poster, setAttributes } ) {
18
+ const posterButtonRef = useRef();
19
+ const descriptionId = useInstanceId(
20
+ PosterImage,
21
+ 'video-block__poster-image-description'
22
+ );
18
23
 
19
24
  function onSelectPoster( image ) {
20
25
  setAttributes( { poster: image.url } );
@@ -24,23 +29,23 @@ function PosterImage( { poster, setAttributes, instanceId } ) {
24
29
  setAttributes( { poster: undefined } );
25
30
 
26
31
  // Move focus back to the Media Upload button.
27
- posterImageButton.current.focus();
32
+ posterButtonRef.current.focus();
28
33
  }
29
34
 
30
35
  return (
31
- <ToolsPanelItem
32
- label={ __( 'Poster image' ) }
33
- isShownByDefault
34
- hasValue={ () => !! poster }
35
- onDeselect={ () => {
36
- setAttributes( { poster: '' } );
37
- } }
38
- >
39
- <MediaUploadCheck>
40
- <div className="editor-video-poster-control">
41
- <BaseControl.VisualLabel>
42
- { __( 'Poster image' ) }
43
- </BaseControl.VisualLabel>
36
+ <MediaUploadCheck>
37
+ <ToolsPanelItem
38
+ label={ __( 'Poster image' ) }
39
+ isShownByDefault
40
+ hasValue={ () => !! poster }
41
+ onDeselect={ () => {
42
+ setAttributes( { poster: undefined } );
43
+ } }
44
+ >
45
+ <BaseControl.VisualLabel>
46
+ { __( 'Poster image' ) }
47
+ </BaseControl.VisualLabel>
48
+ <HStack justify="flex-start">
44
49
  <MediaUpload
45
50
  title={ __( 'Select poster image' ) }
46
51
  onSelect={ onSelectPoster }
@@ -50,14 +55,14 @@ function PosterImage( { poster, setAttributes, instanceId } ) {
50
55
  __next40pxDefaultSize
51
56
  variant="primary"
52
57
  onClick={ open }
53
- ref={ posterImageButton }
54
- aria-describedby={ videoPosterDescription }
58
+ ref={ posterButtonRef }
59
+ aria-describedby={ descriptionId }
55
60
  >
56
61
  { ! poster ? __( 'Select' ) : __( 'Replace' ) }
57
62
  </Button>
58
63
  ) }
59
64
  />
60
- <p id={ videoPosterDescription } hidden>
65
+ <p id={ descriptionId } hidden>
61
66
  { poster
62
67
  ? sprintf(
63
68
  /* translators: %s: poster image URL. */
@@ -77,9 +82,9 @@ function PosterImage( { poster, setAttributes, instanceId } ) {
77
82
  { __( 'Remove' ) }
78
83
  </Button>
79
84
  ) }
80
- </div>
81
- </MediaUploadCheck>
82
- </ToolsPanelItem>
85
+ </HStack>
86
+ </ToolsPanelItem>
87
+ </MediaUploadCheck>
83
88
  );
84
89
  }
85
90
 
@@ -24,9 +24,8 @@ import {
24
24
  MediaUploadCheck,
25
25
  store as blockEditorStore,
26
26
  } from '@wordpress/block-editor';
27
- import { store as noticesStore } from '@wordpress/notices';
28
27
  import { upload, media } from '@wordpress/icons';
29
- import { useSelect, useDispatch } from '@wordpress/data';
28
+ import { useSelect } from '@wordpress/data';
30
29
  import { useState, useRef, useEffect } from '@wordpress/element';
31
30
  import { getFilename } from '@wordpress/url';
32
31
 
@@ -49,11 +48,19 @@ const KIND_OPTIONS = [
49
48
  { label: __( 'Metadata' ), value: 'metadata' },
50
49
  ];
51
50
 
51
+ const DEFAULT_TRACK = {
52
+ src: '',
53
+ label: '',
54
+ srcLang: 'en',
55
+ kind: DEFAULT_KIND,
56
+ default: false,
57
+ };
58
+
52
59
  function TrackList( { tracks, onEditPress } ) {
53
60
  const content = tracks.map( ( track, index ) => {
54
61
  return (
55
62
  <HStack
56
- key={ track.src }
63
+ key={ track.id ?? track.src }
57
64
  className="block-library-video-tracks-editor__track-list-track"
58
65
  >
59
66
  <span>{ track.label }</span>
@@ -93,13 +100,12 @@ function SingleTrackEditor( {
93
100
  onRemove,
94
101
  allowSettingDefault,
95
102
  } ) {
96
- const {
97
- src = '',
98
- label = '',
99
- srcLang = '',
100
- kind = DEFAULT_KIND,
101
- default: isDefaultTrack = false,
102
- } = track;
103
+ const [ trackState, setTrackState ] = useState( {
104
+ ...DEFAULT_TRACK,
105
+ ...track,
106
+ } );
107
+
108
+ const { src, label, srcLang, kind, default: isDefaultTrack } = trackState;
103
109
  const fileName = src.startsWith( 'blob:' ) ? '' : getFilename( src ) || '';
104
110
  return (
105
111
  <VStack
@@ -117,10 +123,10 @@ function SingleTrackEditor( {
117
123
  __next40pxDefaultSize
118
124
  __nextHasNoMarginBottom
119
125
  onChange={ ( newLabel ) =>
120
- onChange( {
121
- ...track,
126
+ setTrackState( ( prevTrackState ) => ( {
127
+ ...prevTrackState,
122
128
  label: newLabel,
123
- } )
129
+ } ) )
124
130
  }
125
131
  label={ __( 'Label' ) }
126
132
  value={ label }
@@ -130,10 +136,10 @@ function SingleTrackEditor( {
130
136
  __next40pxDefaultSize
131
137
  __nextHasNoMarginBottom
132
138
  onChange={ ( newSrcLang ) =>
133
- onChange( {
134
- ...track,
139
+ setTrackState( ( prevTrackState ) => ( {
140
+ ...prevTrackState,
135
141
  srcLang: newSrcLang,
136
- } )
142
+ } ) )
137
143
  }
138
144
  label={ __( 'Source language' ) }
139
145
  value={ srcLang }
@@ -148,12 +154,12 @@ function SingleTrackEditor( {
148
154
  options={ KIND_OPTIONS }
149
155
  value={ kind }
150
156
  label={ __( 'Kind' ) }
151
- onChange={ ( newKind ) => {
152
- onChange( {
153
- ...track,
157
+ onChange={ ( newKind ) =>
158
+ setTrackState( ( prevTrackState ) => ( {
159
+ ...prevTrackState,
154
160
  kind: newKind,
155
- } );
156
- } }
161
+ } ) )
162
+ }
157
163
  />
158
164
  <ToggleControl
159
165
  __next40pxDefaultSize
@@ -161,12 +167,12 @@ function SingleTrackEditor( {
161
167
  label={ __( 'Set as default track' ) }
162
168
  checked={ isDefaultTrack }
163
169
  disabled={ ! allowSettingDefault }
164
- onChange={ ( defaultTrack ) => {
165
- onChange( {
166
- ...track,
170
+ onChange={ ( defaultTrack ) =>
171
+ setTrackState( ( prevTrackState ) => ( {
172
+ ...prevTrackState,
167
173
  default: defaultTrack,
168
- } );
169
- } }
174
+ } ) )
175
+ }
170
176
  />
171
177
  <HStack className="block-library-video-tracks-editor__single-track-editor-buttons-container">
172
178
  <Button
@@ -181,26 +187,7 @@ function SingleTrackEditor( {
181
187
  __next40pxDefaultSize
182
188
  variant="primary"
183
189
  onClick={ () => {
184
- const changes = {};
185
- let hasChanges = false;
186
- if ( label === '' ) {
187
- changes.label = __( 'English' );
188
- hasChanges = true;
189
- }
190
- if ( srcLang === '' ) {
191
- changes.srcLang = 'en';
192
- hasChanges = true;
193
- }
194
- if ( track.kind === undefined ) {
195
- changes.kind = DEFAULT_KIND;
196
- hasChanges = true;
197
- }
198
- if ( hasChanges ) {
199
- onChange( {
200
- ...track,
201
- ...changes,
202
- } );
203
- }
190
+ onChange( trackState );
204
191
  onClose();
205
192
  } }
206
193
  >
@@ -213,27 +200,60 @@ function SingleTrackEditor( {
213
200
  }
214
201
 
215
202
  export default function TracksEditor( { tracks = [], onChange } ) {
216
- const { createNotice } = useDispatch( noticesStore );
217
203
  const mediaUpload = useSelect( ( select ) => {
218
204
  return select( blockEditorStore ).getSettings().mediaUpload;
219
205
  }, [] );
220
206
  const [ trackBeingEdited, setTrackBeingEdited ] = useState( null );
221
207
  const dropdownPopoverRef = useRef();
222
208
 
223
- const handleTrackSelect = ( { title, url } ) => {
224
- if ( tracks.some( ( track ) => track.src === url ) ) {
225
- createNotice( 'error', __( 'This track already exists.' ), {
226
- isDismissible: true,
227
- type: 'snackbar',
228
- } );
209
+ const handleTrackSelect = ( selectedTracks = [], appendTracks = false ) => {
210
+ const existingTracksMap = new Map(
211
+ tracks.map( ( track ) => [ track.id, track ] )
212
+ );
213
+ const tracksToAdd = selectedTracks.map( ( { id, title, url } ) => {
214
+ // Reuse existing tracks to preserve user-configured metadata.
215
+ if ( existingTracksMap.has( id ) ) {
216
+ return existingTracksMap.get( id );
217
+ }
218
+
219
+ return {
220
+ ...DEFAULT_TRACK,
221
+ id,
222
+ label: title || '',
223
+ src: url,
224
+ };
225
+ } );
226
+
227
+ if ( tracksToAdd.length === 0 ) {
229
228
  return;
230
229
  }
231
230
 
232
- const trackIndex = tracks.length;
233
- onChange( [ ...tracks, { label: title || '', src: url } ] );
234
- setTrackBeingEdited( trackIndex );
231
+ onChange( [ ...( appendTracks ? tracks : [] ), ...tracksToAdd ] );
235
232
  };
236
233
 
234
+ function uploadFiles( event ) {
235
+ const files = event.target.files;
236
+ mediaUpload( {
237
+ allowedTypes: ALLOWED_TYPES,
238
+ filesList: files,
239
+ onFileChange: ( selectedTracks ) => {
240
+ if ( ! Array.isArray( selectedTracks ) ) {
241
+ return;
242
+ }
243
+
244
+ // Wait until the track has been uploaded.
245
+ const uploadedTracks = selectedTracks.filter(
246
+ ( track ) => !! track?.id
247
+ );
248
+
249
+ if ( ! uploadedTracks.length ) {
250
+ return;
251
+ }
252
+ handleTrackSelect( uploadedTracks, true );
253
+ },
254
+ } );
255
+ }
256
+
237
257
  useEffect( () => {
238
258
  dropdownPopoverRef.current?.focus();
239
259
  }, [ trackBeingEdited ] );
@@ -321,60 +341,30 @@ export default function TracksEditor( { tracks = [], onChange } ) {
321
341
  className="block-library-video-tracks-editor__add-tracks-container"
322
342
  label={ __( 'Add tracks' ) }
323
343
  >
324
- <MediaUpload
325
- onSelect={ handleTrackSelect }
326
- allowedTypes={ ALLOWED_TYPES }
327
- render={ ( { open } ) => (
328
- <MenuItem
329
- icon={ media }
330
- onClick={ open }
331
- >
332
- { __( 'Open Media Library' ) }
333
- </MenuItem>
334
- ) }
335
- />
336
344
  <MediaUploadCheck>
345
+ <MediaUpload
346
+ onSelect={ handleTrackSelect }
347
+ allowedTypes={ ALLOWED_TYPES }
348
+ value={ tracks.map( ( { id } ) => id ) }
349
+ multiple
350
+ render={ ( { open } ) => (
351
+ <MenuItem
352
+ icon={ media }
353
+ onClick={ open }
354
+ >
355
+ { __( 'Open Media Library' ) }
356
+ </MenuItem>
357
+ ) }
358
+ />
337
359
  <FormFileUpload
338
- onChange={ ( event ) => {
339
- const files = event.target.files;
340
- const trackIndex = tracks.length;
341
- mediaUpload( {
342
- allowedTypes: ALLOWED_TYPES,
343
- filesList: files,
344
- onFileChange: ( [
345
- { url },
346
- ] ) => {
347
- const newTracks = [
348
- ...tracks,
349
- ];
350
- if (
351
- ! newTracks[
352
- trackIndex
353
- ]
354
- ) {
355
- newTracks[
356
- trackIndex
357
- ] = {};
358
- }
359
- newTracks[ trackIndex ] = {
360
- ...tracks[ trackIndex ],
361
- src: url,
362
- };
363
- onChange( newTracks );
364
- setTrackBeingEdited(
365
- trackIndex
366
- );
367
- },
368
- } );
369
- } }
360
+ onChange={ uploadFiles }
370
361
  accept=".vtt,text/vtt"
362
+ multiple
371
363
  render={ ( { openFileDialog } ) => {
372
364
  return (
373
365
  <MenuItem
374
366
  icon={ upload }
375
- onClick={ () => {
376
- openFileDialog();
377
- } }
367
+ onClick={ openFileDialog }
378
368
  >
379
369
  { _x( 'Upload', 'verb' ) }
380
370
  </MenuItem>
@@ -1,5 +1,6 @@
1
1
  export default function Tracks( { tracks = [] } ) {
2
2
  return tracks.map( ( track ) => {
3
- return <track key={ track.src } { ...track } />;
3
+ const { id, ...trackAttrs } = track;
4
+ return <track key={ id ?? trackAttrs.src } { ...trackAttrs } />;
4
5
  } );
5
6
  }