@wordpress/block-editor 14.2.1-next.5368f64a9.0 → 14.3.1

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 (144) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/components/background-image-control/index.js +566 -0
  3. package/build/components/background-image-control/index.js.map +1 -0
  4. package/build/components/block-card/index.js +5 -2
  5. package/build/components/block-card/index.js.map +1 -1
  6. package/build/components/block-list/use-block-props/use-zoom-out-mode-exit.js +4 -2
  7. package/build/components/block-list/use-block-props/use-zoom-out-mode-exit.js.map +1 -1
  8. package/build/components/global-styles/background-panel.js +20 -545
  9. package/build/components/global-styles/background-panel.js.map +1 -1
  10. package/build/components/global-styles/dimensions-panel.js +3 -0
  11. package/build/components/global-styles/dimensions-panel.js.map +1 -1
  12. package/build/components/grid/grid-item-resizer.js +2 -2
  13. package/build/components/grid/grid-item-resizer.js.map +1 -1
  14. package/build/components/iframe/index.js +1 -0
  15. package/build/components/iframe/index.js.map +1 -1
  16. package/build/components/image-editor/use-save-image.js +6 -0
  17. package/build/components/image-editor/use-save-image.js.map +1 -1
  18. package/build/components/image-editor/use-transform-image.js +1 -0
  19. package/build/components/image-editor/use-transform-image.js.map +1 -1
  20. package/build/components/inserter/block-patterns-explorer/pattern-explorer-sidebar.js +2 -4
  21. package/build/components/inserter/block-patterns-explorer/pattern-explorer-sidebar.js.map +1 -1
  22. package/build/components/inserter/block-patterns-tab/index.js +2 -4
  23. package/build/components/inserter/block-patterns-tab/index.js.map +1 -1
  24. package/build/components/inserter/media-tab/media-preview.js +4 -8
  25. package/build/components/inserter/media-tab/media-preview.js.map +1 -1
  26. package/build/components/inserter/media-tab/media-tab.js +2 -4
  27. package/build/components/inserter/media-tab/media-tab.js.map +1 -1
  28. package/build/components/inserter/quick-inserter.js +2 -4
  29. package/build/components/inserter/quick-inserter.js.map +1 -1
  30. package/build/components/inserter-listbox/item.js +2 -4
  31. package/build/components/inserter-listbox/item.js.map +1 -1
  32. package/build/components/link-control/index.js +14 -14
  33. package/build/components/link-control/index.js.map +1 -1
  34. package/build/components/link-control/search-input.js +4 -2
  35. package/build/components/link-control/search-input.js.map +1 -1
  36. package/build/components/rich-text/index.js +10 -4
  37. package/build/components/rich-text/index.js.map +1 -1
  38. package/build/components/spacing-sizes-control/index.js +8 -9
  39. package/build/components/spacing-sizes-control/index.js.map +1 -1
  40. package/build/components/spacing-sizes-control/linked-button.js +35 -0
  41. package/build/components/spacing-sizes-control/linked-button.js.map +1 -0
  42. package/build/components/spacing-sizes-control/utils.js +3 -2
  43. package/build/components/spacing-sizes-control/utils.js.map +1 -1
  44. package/build/components/url-input/index.js +7 -6
  45. package/build/components/url-input/index.js.map +1 -1
  46. package/build/hooks/block-bindings.js +65 -53
  47. package/build/hooks/block-bindings.js.map +1 -1
  48. package/build/hooks/block-hooks.js +1 -8
  49. package/build/hooks/block-hooks.js.map +1 -1
  50. package/build/store/private-selectors.js +10 -0
  51. package/build/store/private-selectors.js.map +1 -1
  52. package/build-module/components/background-image-control/index.js +556 -0
  53. package/build-module/components/background-image-control/index.js.map +1 -0
  54. package/build-module/components/block-card/index.js +6 -3
  55. package/build-module/components/block-card/index.js.map +1 -1
  56. package/build-module/components/block-list/use-block-props/use-zoom-out-mode-exit.js +4 -2
  57. package/build-module/components/block-list/use-block-props/use-zoom-out-mode-exit.js.map +1 -1
  58. package/build-module/components/global-styles/background-panel.js +22 -546
  59. package/build-module/components/global-styles/background-panel.js.map +1 -1
  60. package/build-module/components/global-styles/dimensions-panel.js +3 -0
  61. package/build-module/components/global-styles/dimensions-panel.js.map +1 -1
  62. package/build-module/components/grid/grid-item-resizer.js +2 -2
  63. package/build-module/components/grid/grid-item-resizer.js.map +1 -1
  64. package/build-module/components/iframe/index.js +1 -0
  65. package/build-module/components/iframe/index.js.map +1 -1
  66. package/build-module/components/image-editor/use-save-image.js +6 -0
  67. package/build-module/components/image-editor/use-save-image.js.map +1 -1
  68. package/build-module/components/image-editor/use-transform-image.js +1 -0
  69. package/build-module/components/image-editor/use-transform-image.js.map +1 -1
  70. package/build-module/components/inserter/block-patterns-explorer/pattern-explorer-sidebar.js +2 -4
  71. package/build-module/components/inserter/block-patterns-explorer/pattern-explorer-sidebar.js.map +1 -1
  72. package/build-module/components/inserter/block-patterns-tab/index.js +2 -4
  73. package/build-module/components/inserter/block-patterns-tab/index.js.map +1 -1
  74. package/build-module/components/inserter/media-tab/media-preview.js +4 -8
  75. package/build-module/components/inserter/media-tab/media-preview.js.map +1 -1
  76. package/build-module/components/inserter/media-tab/media-tab.js +2 -4
  77. package/build-module/components/inserter/media-tab/media-tab.js.map +1 -1
  78. package/build-module/components/inserter/quick-inserter.js +2 -4
  79. package/build-module/components/inserter/quick-inserter.js.map +1 -1
  80. package/build-module/components/inserter-listbox/item.js +2 -4
  81. package/build-module/components/inserter-listbox/item.js.map +1 -1
  82. package/build-module/components/link-control/index.js +15 -15
  83. package/build-module/components/link-control/index.js.map +1 -1
  84. package/build-module/components/link-control/search-input.js +4 -2
  85. package/build-module/components/link-control/search-input.js.map +1 -1
  86. package/build-module/components/rich-text/index.js +10 -4
  87. package/build-module/components/rich-text/index.js.map +1 -1
  88. package/build-module/components/spacing-sizes-control/index.js +9 -10
  89. package/build-module/components/spacing-sizes-control/index.js.map +1 -1
  90. package/build-module/components/spacing-sizes-control/linked-button.js +28 -0
  91. package/build-module/components/spacing-sizes-control/linked-button.js.map +1 -0
  92. package/build-module/components/spacing-sizes-control/utils.js +3 -2
  93. package/build-module/components/spacing-sizes-control/utils.js.map +1 -1
  94. package/build-module/components/url-input/index.js +8 -7
  95. package/build-module/components/url-input/index.js.map +1 -1
  96. package/build-module/hooks/block-bindings.js +65 -53
  97. package/build-module/hooks/block-bindings.js.map +1 -1
  98. package/build-module/hooks/block-hooks.js +3 -10
  99. package/build-module/hooks/block-hooks.js.map +1 -1
  100. package/build-module/store/private-selectors.js +10 -0
  101. package/build-module/store/private-selectors.js.map +1 -1
  102. package/build-style/style-rtl.css +152 -285
  103. package/build-style/style.css +152 -285
  104. package/package.json +32 -32
  105. package/src/components/background-image-control/index.js +741 -0
  106. package/src/components/background-image-control/style.scss +170 -0
  107. package/src/components/background-image-control/test/index.js +47 -0
  108. package/src/components/block-card/index.js +12 -3
  109. package/src/components/block-list/use-block-props/use-zoom-out-mode-exit.js +2 -5
  110. package/src/components/global-styles/background-panel.js +19 -730
  111. package/src/components/global-styles/dimensions-panel.js +3 -0
  112. package/src/components/global-styles/style.scss +0 -168
  113. package/src/components/global-styles/test/background-panel.js +1 -47
  114. package/src/components/grid/grid-item-resizer.js +2 -2
  115. package/src/components/image-editor/use-save-image.js +7 -0
  116. package/src/components/inserter/block-patterns-explorer/pattern-explorer-sidebar.js +1 -2
  117. package/src/components/inserter/block-patterns-tab/index.js +1 -2
  118. package/src/components/inserter/media-tab/media-preview.js +2 -4
  119. package/src/components/inserter/media-tab/media-tab.js +1 -2
  120. package/src/components/inserter/quick-inserter.js +1 -2
  121. package/src/components/inserter/style.scss +0 -10
  122. package/src/components/inserter-listbox/item.js +1 -5
  123. package/src/components/link-control/index.js +19 -14
  124. package/src/components/link-control/search-input.js +2 -0
  125. package/src/components/link-control/style.scss +0 -22
  126. package/src/components/list-view/style.scss +1 -1
  127. package/src/components/rich-text/index.js +20 -5
  128. package/src/components/spacing-sizes-control/index.js +10 -13
  129. package/src/components/spacing-sizes-control/linked-button.js +32 -0
  130. package/src/components/spacing-sizes-control/test/utils.js +22 -30
  131. package/src/components/spacing-sizes-control/utils.js +6 -2
  132. package/src/components/url-input/index.js +5 -4
  133. package/src/components/url-input/style.scss +3 -26
  134. package/src/hooks/block-bindings.js +64 -50
  135. package/src/hooks/block-hooks.js +3 -14
  136. package/src/hooks/block-hooks.scss +0 -9
  137. package/src/store/private-selectors.js +9 -0
  138. package/src/style.scss +1 -0
  139. package/src/utils/test/transform-styles.js +1 -1
  140. package/build/components/spacing-sizes-control/sides-dropdown/index.js +0 -86
  141. package/build/components/spacing-sizes-control/sides-dropdown/index.js.map +0 -1
  142. package/build-module/components/spacing-sizes-control/sides-dropdown/index.js +0 -81
  143. package/build-module/components/spacing-sizes-control/sides-dropdown/index.js.map +0 -1
  144. package/src/components/spacing-sizes-control/sides-dropdown/index.js +0 -91
@@ -0,0 +1,741 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import clsx from 'clsx';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import {
10
+ ToggleControl,
11
+ __experimentalToggleGroupControl as ToggleGroupControl,
12
+ __experimentalToggleGroupControlOption as ToggleGroupControlOption,
13
+ __experimentalUnitControl as UnitControl,
14
+ __experimentalVStack as VStack,
15
+ DropZone,
16
+ FlexItem,
17
+ FocalPointPicker,
18
+ MenuItem,
19
+ VisuallyHidden,
20
+ __experimentalItemGroup as ItemGroup,
21
+ __experimentalHStack as HStack,
22
+ __experimentalTruncate as Truncate,
23
+ Dropdown,
24
+ Placeholder,
25
+ Spinner,
26
+ __experimentalDropdownContentWrapper as DropdownContentWrapper,
27
+ } from '@wordpress/components';
28
+ import { __, _x, sprintf } from '@wordpress/i18n';
29
+ import { store as noticesStore } from '@wordpress/notices';
30
+ import { getFilename } from '@wordpress/url';
31
+ import { useRef, useState, useEffect, useMemo } from '@wordpress/element';
32
+ import { useDispatch, useSelect } from '@wordpress/data';
33
+ import { focus } from '@wordpress/dom';
34
+ import { isBlobURL } from '@wordpress/blob';
35
+
36
+ /**
37
+ * Internal dependencies
38
+ */
39
+ import { getResolvedValue } from '../global-styles/utils';
40
+ import { hasBackgroundImageValue } from '../global-styles/background-panel';
41
+ import { setImmutably } from '../../utils/object';
42
+ import MediaReplaceFlow from '../media-replace-flow';
43
+ import { store as blockEditorStore } from '../../store';
44
+
45
+ import {
46
+ globalStylesDataKey,
47
+ globalStylesLinksDataKey,
48
+ } from '../../store/private-keys';
49
+
50
+ const IMAGE_BACKGROUND_TYPE = 'image';
51
+
52
+ const BACKGROUND_POPOVER_PROPS = {
53
+ placement: 'left-start',
54
+ offset: 36,
55
+ shift: true,
56
+ className: 'block-editor-global-styles-background-panel__popover',
57
+ };
58
+ const noop = () => {};
59
+
60
+ /**
61
+ * Get the help text for the background size control.
62
+ *
63
+ * @param {string} value backgroundSize value.
64
+ * @return {string} Translated help text.
65
+ */
66
+ function backgroundSizeHelpText( value ) {
67
+ if ( value === 'cover' || value === undefined ) {
68
+ return __( 'Image covers the space evenly.' );
69
+ }
70
+ if ( value === 'contain' ) {
71
+ return __( 'Image is contained without distortion.' );
72
+ }
73
+ return __( 'Image has a fixed width.' );
74
+ }
75
+
76
+ /**
77
+ * Converts decimal x and y coords from FocalPointPicker to percentage-based values
78
+ * to use as backgroundPosition value.
79
+ *
80
+ * @param {{x?:number, y?:number}} value FocalPointPicker coords.
81
+ * @return {string} backgroundPosition value.
82
+ */
83
+ export const coordsToBackgroundPosition = ( value ) => {
84
+ if ( ! value || ( isNaN( value.x ) && isNaN( value.y ) ) ) {
85
+ return undefined;
86
+ }
87
+
88
+ const x = isNaN( value.x ) ? 0.5 : value.x;
89
+ const y = isNaN( value.y ) ? 0.5 : value.y;
90
+
91
+ return `${ x * 100 }% ${ y * 100 }%`;
92
+ };
93
+
94
+ /**
95
+ * Converts backgroundPosition value to x and y coords for FocalPointPicker.
96
+ *
97
+ * @param {string} value backgroundPosition value.
98
+ * @return {{x?:number, y?:number}} FocalPointPicker coords.
99
+ */
100
+ export const backgroundPositionToCoords = ( value ) => {
101
+ if ( ! value ) {
102
+ return { x: undefined, y: undefined };
103
+ }
104
+
105
+ let [ x, y ] = value.split( ' ' ).map( ( v ) => parseFloat( v ) / 100 );
106
+ x = isNaN( x ) ? undefined : x;
107
+ y = isNaN( y ) ? x : y;
108
+
109
+ return { x, y };
110
+ };
111
+
112
+ function InspectorImagePreviewItem( {
113
+ as = 'span',
114
+ imgUrl,
115
+ toggleProps = {},
116
+ filename,
117
+ label,
118
+ className,
119
+ onToggleCallback = noop,
120
+ } ) {
121
+ useEffect( () => {
122
+ if ( typeof toggleProps?.isOpen !== 'undefined' ) {
123
+ onToggleCallback( toggleProps?.isOpen );
124
+ }
125
+ }, [ toggleProps?.isOpen, onToggleCallback ] );
126
+ return (
127
+ <ItemGroup as={ as } className={ className } { ...toggleProps }>
128
+ <HStack
129
+ justify="flex-start"
130
+ as="span"
131
+ className="block-editor-global-styles-background-panel__inspector-preview-inner"
132
+ >
133
+ { imgUrl && (
134
+ <span
135
+ className="block-editor-global-styles-background-panel__inspector-image-indicator-wrapper"
136
+ aria-hidden
137
+ >
138
+ <span
139
+ className="block-editor-global-styles-background-panel__inspector-image-indicator"
140
+ style={ {
141
+ backgroundImage: `url(${ imgUrl })`,
142
+ } }
143
+ />
144
+ </span>
145
+ ) }
146
+ <FlexItem as="span" style={ imgUrl ? {} : { flexGrow: 1 } }>
147
+ <Truncate
148
+ numberOfLines={ 1 }
149
+ className="block-editor-global-styles-background-panel__inspector-media-replace-title"
150
+ >
151
+ { label }
152
+ </Truncate>
153
+ <VisuallyHidden as="span">
154
+ { imgUrl
155
+ ? sprintf(
156
+ /* translators: %s: file name */
157
+ __( 'Background image: %s' ),
158
+ filename || label
159
+ )
160
+ : __( 'No background image selected' ) }
161
+ </VisuallyHidden>
162
+ </FlexItem>
163
+ </HStack>
164
+ </ItemGroup>
165
+ );
166
+ }
167
+
168
+ function BackgroundControlsPanel( {
169
+ label,
170
+ filename,
171
+ url: imgUrl,
172
+ children,
173
+ onToggle: onToggleCallback = noop,
174
+ hasImageValue,
175
+ } ) {
176
+ if ( ! hasImageValue ) {
177
+ return;
178
+ }
179
+
180
+ const imgLabel =
181
+ label || getFilename( imgUrl ) || __( 'Add background image' );
182
+
183
+ return (
184
+ <Dropdown
185
+ popoverProps={ BACKGROUND_POPOVER_PROPS }
186
+ renderToggle={ ( { onToggle, isOpen } ) => {
187
+ const toggleProps = {
188
+ onClick: onToggle,
189
+ className:
190
+ 'block-editor-global-styles-background-panel__dropdown-toggle',
191
+ 'aria-expanded': isOpen,
192
+ 'aria-label': __(
193
+ 'Background size, position and repeat options.'
194
+ ),
195
+ isOpen,
196
+ };
197
+ return (
198
+ <InspectorImagePreviewItem
199
+ imgUrl={ imgUrl }
200
+ filename={ filename }
201
+ label={ imgLabel }
202
+ toggleProps={ toggleProps }
203
+ as="button"
204
+ onToggleCallback={ onToggleCallback }
205
+ />
206
+ );
207
+ } }
208
+ renderContent={ () => (
209
+ <DropdownContentWrapper
210
+ className="block-editor-global-styles-background-panel__dropdown-content-wrapper"
211
+ paddingSize="medium"
212
+ >
213
+ { children }
214
+ </DropdownContentWrapper>
215
+ ) }
216
+ />
217
+ );
218
+ }
219
+
220
+ function LoadingSpinner() {
221
+ return (
222
+ <Placeholder className="block-editor-global-styles-background-panel__loading">
223
+ <Spinner />
224
+ </Placeholder>
225
+ );
226
+ }
227
+
228
+ function BackgroundImageControls( {
229
+ onChange,
230
+ style,
231
+ inheritedValue,
232
+ onRemoveImage = noop,
233
+ onResetImage = noop,
234
+ displayInPanel,
235
+ defaultValues,
236
+ } ) {
237
+ const [ isUploading, setIsUploading ] = useState( false );
238
+ const { getSettings } = useSelect( blockEditorStore );
239
+
240
+ const { id, title, url } = style?.background?.backgroundImage || {
241
+ ...inheritedValue?.background?.backgroundImage,
242
+ };
243
+ const replaceContainerRef = useRef();
244
+ const { createErrorNotice } = useDispatch( noticesStore );
245
+ const onUploadError = ( message ) => {
246
+ createErrorNotice( message, { type: 'snackbar' } );
247
+ setIsUploading( false );
248
+ };
249
+
250
+ const resetBackgroundImage = () =>
251
+ onChange(
252
+ setImmutably(
253
+ style,
254
+ [ 'background', 'backgroundImage' ],
255
+ undefined
256
+ )
257
+ );
258
+
259
+ const onSelectMedia = ( media ) => {
260
+ if ( ! media || ! media.url ) {
261
+ resetBackgroundImage();
262
+ setIsUploading( false );
263
+ return;
264
+ }
265
+
266
+ if ( isBlobURL( media.url ) ) {
267
+ setIsUploading( true );
268
+ return;
269
+ }
270
+
271
+ // For media selections originated from a file upload.
272
+ if (
273
+ ( media.media_type &&
274
+ media.media_type !== IMAGE_BACKGROUND_TYPE ) ||
275
+ ( ! media.media_type &&
276
+ media.type &&
277
+ media.type !== IMAGE_BACKGROUND_TYPE )
278
+ ) {
279
+ onUploadError(
280
+ __( 'Only images can be used as a background image.' )
281
+ );
282
+ return;
283
+ }
284
+
285
+ const sizeValue =
286
+ style?.background?.backgroundSize || defaultValues?.backgroundSize;
287
+ const positionValue = style?.background?.backgroundPosition;
288
+ onChange(
289
+ setImmutably( style, [ 'background' ], {
290
+ ...style?.background,
291
+ backgroundImage: {
292
+ url: media.url,
293
+ id: media.id,
294
+ source: 'file',
295
+ title: media.title || undefined,
296
+ },
297
+ backgroundPosition:
298
+ /*
299
+ * A background image uploaded and set in the editor receives a default background position of '50% 0',
300
+ * when the background image size is the equivalent of "Tile".
301
+ * This is to increase the chance that the image's focus point is visible.
302
+ * This is in-editor only to assist with the user experience.
303
+ */
304
+ ! positionValue && ( 'auto' === sizeValue || ! sizeValue )
305
+ ? '50% 0'
306
+ : positionValue,
307
+ backgroundSize: sizeValue,
308
+ } )
309
+ );
310
+ setIsUploading( false );
311
+ };
312
+
313
+ // Drag and drop callback, restricting image to one.
314
+ const onFilesDrop = ( filesList ) => {
315
+ if ( filesList?.length > 1 ) {
316
+ onUploadError(
317
+ __( 'Only one image can be used as a background image.' )
318
+ );
319
+ return;
320
+ }
321
+ getSettings().mediaUpload( {
322
+ allowedTypes: [ IMAGE_BACKGROUND_TYPE ],
323
+ filesList,
324
+ onFileChange( [ image ] ) {
325
+ onSelectMedia( image );
326
+ },
327
+ onError: onUploadError,
328
+ } );
329
+ };
330
+
331
+ const hasValue = hasBackgroundImageValue( style );
332
+
333
+ const closeAndFocus = () => {
334
+ const [ toggleButton ] = focus.tabbable.find(
335
+ replaceContainerRef.current
336
+ );
337
+ // Focus the toggle button and close the dropdown menu.
338
+ // This ensures similar behaviour as to selecting an image, where the dropdown is
339
+ // closed and focus is redirected to the dropdown toggle button.
340
+ toggleButton?.focus();
341
+ toggleButton?.click();
342
+ };
343
+
344
+ const onRemove = () =>
345
+ onChange(
346
+ setImmutably( style, [ 'background' ], {
347
+ backgroundImage: 'none',
348
+ } )
349
+ );
350
+ const canRemove = ! hasValue && hasBackgroundImageValue( inheritedValue );
351
+ const imgLabel =
352
+ title || getFilename( url ) || __( 'Add background image' );
353
+
354
+ return (
355
+ <div
356
+ ref={ replaceContainerRef }
357
+ className="block-editor-global-styles-background-panel__image-tools-panel-item"
358
+ >
359
+ { isUploading && <LoadingSpinner /> }
360
+ <MediaReplaceFlow
361
+ mediaId={ id }
362
+ mediaURL={ url }
363
+ allowedTypes={ [ IMAGE_BACKGROUND_TYPE ] }
364
+ accept="image/*"
365
+ onSelect={ onSelectMedia }
366
+ popoverProps={ {
367
+ className: clsx( {
368
+ 'block-editor-global-styles-background-panel__media-replace-popover':
369
+ displayInPanel,
370
+ } ),
371
+ } }
372
+ name={
373
+ <InspectorImagePreviewItem
374
+ className="block-editor-global-styles-background-panel__image-preview"
375
+ imgUrl={ url }
376
+ filename={ title }
377
+ label={ imgLabel }
378
+ />
379
+ }
380
+ variant="secondary"
381
+ onError={ onUploadError }
382
+ onReset={ () => {
383
+ closeAndFocus();
384
+ onResetImage();
385
+ } }
386
+ >
387
+ { canRemove && (
388
+ <MenuItem
389
+ onClick={ () => {
390
+ closeAndFocus();
391
+ onRemove();
392
+ onRemoveImage();
393
+ } }
394
+ >
395
+ { __( 'Remove' ) }
396
+ </MenuItem>
397
+ ) }
398
+ </MediaReplaceFlow>
399
+ <DropZone
400
+ onFilesDrop={ onFilesDrop }
401
+ label={ __( 'Drop to upload' ) }
402
+ />
403
+ </div>
404
+ );
405
+ }
406
+
407
+ function BackgroundSizeControls( {
408
+ onChange,
409
+ style,
410
+ inheritedValue,
411
+ defaultValues,
412
+ } ) {
413
+ const sizeValue =
414
+ style?.background?.backgroundSize ||
415
+ inheritedValue?.background?.backgroundSize;
416
+ const repeatValue =
417
+ style?.background?.backgroundRepeat ||
418
+ inheritedValue?.background?.backgroundRepeat;
419
+ const imageValue =
420
+ style?.background?.backgroundImage?.url ||
421
+ inheritedValue?.background?.backgroundImage?.url;
422
+ const isUploadedImage = style?.background?.backgroundImage?.id;
423
+ const positionValue =
424
+ style?.background?.backgroundPosition ||
425
+ inheritedValue?.background?.backgroundPosition;
426
+ const attachmentValue =
427
+ style?.background?.backgroundAttachment ||
428
+ inheritedValue?.background?.backgroundAttachment;
429
+
430
+ /*
431
+ * Set default values for uploaded images.
432
+ * The default values are passed by the consumer.
433
+ * Block-level controls may have different defaults to root-level controls.
434
+ * A falsy value is treated by default as `auto` (Tile).
435
+ */
436
+ let currentValueForToggle =
437
+ ! sizeValue && isUploadedImage
438
+ ? defaultValues?.backgroundSize
439
+ : sizeValue || 'auto';
440
+ /*
441
+ * The incoming value could be a value + unit, e.g. '20px'.
442
+ * In this case set the value to 'tile'.
443
+ */
444
+ currentValueForToggle = ! [ 'cover', 'contain', 'auto' ].includes(
445
+ currentValueForToggle
446
+ )
447
+ ? 'auto'
448
+ : currentValueForToggle;
449
+ /*
450
+ * If the current value is `cover` and the repeat value is `undefined`, then
451
+ * the toggle should be unchecked as the default state. Otherwise, the toggle
452
+ * should reflect the current repeat value.
453
+ */
454
+ const repeatCheckedValue = ! (
455
+ repeatValue === 'no-repeat' ||
456
+ ( currentValueForToggle === 'cover' && repeatValue === undefined )
457
+ );
458
+
459
+ const updateBackgroundSize = ( next ) => {
460
+ // When switching to 'contain' toggle the repeat off.
461
+ let nextRepeat = repeatValue;
462
+ let nextPosition = positionValue;
463
+
464
+ if ( next === 'contain' ) {
465
+ nextRepeat = 'no-repeat';
466
+ nextPosition = undefined;
467
+ }
468
+
469
+ if ( next === 'cover' ) {
470
+ nextRepeat = undefined;
471
+ nextPosition = undefined;
472
+ }
473
+
474
+ if (
475
+ ( currentValueForToggle === 'cover' ||
476
+ currentValueForToggle === 'contain' ) &&
477
+ next === 'auto'
478
+ ) {
479
+ nextRepeat = undefined;
480
+ /*
481
+ * A background image uploaded and set in the editor (an image with a record id),
482
+ * receives a default background position of '50% 0',
483
+ * when the toggle switches to "Tile". This is to increase the chance that
484
+ * the image's focus point is visible.
485
+ * This is in-editor only to assist with the user experience.
486
+ */
487
+ if ( !! style?.background?.backgroundImage?.id ) {
488
+ nextPosition = '50% 0';
489
+ }
490
+ }
491
+
492
+ /*
493
+ * Next will be null when the input is cleared,
494
+ * in which case the value should be 'auto'.
495
+ */
496
+ if ( ! next && currentValueForToggle === 'auto' ) {
497
+ next = 'auto';
498
+ }
499
+
500
+ onChange(
501
+ setImmutably( style, [ 'background' ], {
502
+ ...style?.background,
503
+ backgroundPosition: nextPosition,
504
+ backgroundRepeat: nextRepeat,
505
+ backgroundSize: next,
506
+ } )
507
+ );
508
+ };
509
+
510
+ const updateBackgroundPosition = ( next ) => {
511
+ onChange(
512
+ setImmutably(
513
+ style,
514
+ [ 'background', 'backgroundPosition' ],
515
+ coordsToBackgroundPosition( next )
516
+ )
517
+ );
518
+ };
519
+
520
+ const toggleIsRepeated = () =>
521
+ onChange(
522
+ setImmutably(
523
+ style,
524
+ [ 'background', 'backgroundRepeat' ],
525
+ repeatCheckedValue === true ? 'no-repeat' : 'repeat'
526
+ )
527
+ );
528
+
529
+ const toggleScrollWithPage = () =>
530
+ onChange(
531
+ setImmutably(
532
+ style,
533
+ [ 'background', 'backgroundAttachment' ],
534
+ attachmentValue === 'fixed' ? 'scroll' : 'fixed'
535
+ )
536
+ );
537
+
538
+ // Set a default background position for non-site-wide, uploaded images with a size of 'contain'.
539
+ const backgroundPositionValue =
540
+ ! positionValue && isUploadedImage && 'contain' === sizeValue
541
+ ? defaultValues?.backgroundPosition
542
+ : positionValue;
543
+
544
+ return (
545
+ <VStack spacing={ 3 } className="single-column">
546
+ <FocalPointPicker
547
+ __nextHasNoMarginBottom
548
+ label={ __( 'Focal point' ) }
549
+ url={ imageValue }
550
+ value={ backgroundPositionToCoords( backgroundPositionValue ) }
551
+ onChange={ updateBackgroundPosition }
552
+ />
553
+ <ToggleControl
554
+ __nextHasNoMarginBottom
555
+ label={ __( 'Fixed background' ) }
556
+ checked={ attachmentValue === 'fixed' }
557
+ onChange={ toggleScrollWithPage }
558
+ />
559
+ <ToggleGroupControl
560
+ __nextHasNoMarginBottom
561
+ size="__unstable-large"
562
+ label={ __( 'Size' ) }
563
+ value={ currentValueForToggle }
564
+ onChange={ updateBackgroundSize }
565
+ isBlock
566
+ help={ backgroundSizeHelpText(
567
+ sizeValue || defaultValues?.backgroundSize
568
+ ) }
569
+ >
570
+ <ToggleGroupControlOption
571
+ key="cover"
572
+ value="cover"
573
+ label={ _x(
574
+ 'Cover',
575
+ 'Size option for background image control'
576
+ ) }
577
+ />
578
+ <ToggleGroupControlOption
579
+ key="contain"
580
+ value="contain"
581
+ label={ _x(
582
+ 'Contain',
583
+ 'Size option for background image control'
584
+ ) }
585
+ />
586
+ <ToggleGroupControlOption
587
+ key="tile"
588
+ value="auto"
589
+ label={ _x(
590
+ 'Tile',
591
+ 'Size option for background image control'
592
+ ) }
593
+ />
594
+ </ToggleGroupControl>
595
+ <HStack justify="flex-start" spacing={ 2 } as="span">
596
+ <UnitControl
597
+ aria-label={ __( 'Background image width' ) }
598
+ onChange={ updateBackgroundSize }
599
+ value={ sizeValue }
600
+ size="__unstable-large"
601
+ __unstableInputWidth="100px"
602
+ min={ 0 }
603
+ placeholder={ __( 'Auto' ) }
604
+ disabled={
605
+ currentValueForToggle !== 'auto' ||
606
+ currentValueForToggle === undefined
607
+ }
608
+ />
609
+ <ToggleControl
610
+ __nextHasNoMarginBottom
611
+ label={ __( 'Repeat' ) }
612
+ checked={ repeatCheckedValue }
613
+ onChange={ toggleIsRepeated }
614
+ disabled={ currentValueForToggle === 'cover' }
615
+ />
616
+ </HStack>
617
+ </VStack>
618
+ );
619
+ }
620
+
621
+ export default function BackgroundImagePanel( {
622
+ value,
623
+ onChange,
624
+ inheritedValue = value,
625
+ settings,
626
+ defaultValues = {},
627
+ } ) {
628
+ /*
629
+ * Resolve any inherited "ref" pointers.
630
+ * Should the block editor need resolved, inherited values
631
+ * across all controls, this could be abstracted into a hook,
632
+ * e.g., useResolveGlobalStyle
633
+ */
634
+ const { globalStyles, _links } = useSelect( ( select ) => {
635
+ const { getSettings } = select( blockEditorStore );
636
+ const _settings = getSettings();
637
+ return {
638
+ globalStyles: _settings[ globalStylesDataKey ],
639
+ _links: _settings[ globalStylesLinksDataKey ],
640
+ };
641
+ }, [] );
642
+ const resolvedInheritedValue = useMemo( () => {
643
+ const resolvedValues = {
644
+ background: {},
645
+ };
646
+
647
+ if ( ! inheritedValue?.background ) {
648
+ return inheritedValue;
649
+ }
650
+
651
+ Object.entries( inheritedValue?.background ).forEach(
652
+ ( [ key, backgroundValue ] ) => {
653
+ resolvedValues.background[ key ] = getResolvedValue(
654
+ backgroundValue,
655
+ {
656
+ styles: globalStyles,
657
+ _links,
658
+ }
659
+ );
660
+ }
661
+ );
662
+ return resolvedValues;
663
+ }, [ globalStyles, _links, inheritedValue ] );
664
+
665
+ const resetBackground = () =>
666
+ onChange( setImmutably( value, [ 'background' ], {} ) );
667
+
668
+ const { title, url } = value?.background?.backgroundImage || {
669
+ ...resolvedInheritedValue?.background?.backgroundImage,
670
+ };
671
+ const hasImageValue =
672
+ hasBackgroundImageValue( value ) ||
673
+ hasBackgroundImageValue( resolvedInheritedValue );
674
+
675
+ const imageValue =
676
+ value?.background?.backgroundImage ||
677
+ inheritedValue?.background?.backgroundImage;
678
+
679
+ const shouldShowBackgroundImageControls =
680
+ hasImageValue &&
681
+ 'none' !== imageValue &&
682
+ ( settings?.background?.backgroundSize ||
683
+ settings?.background?.backgroundPosition ||
684
+ settings?.background?.backgroundRepeat );
685
+
686
+ const [ isDropDownOpen, setIsDropDownOpen ] = useState( false );
687
+
688
+ return (
689
+ <div
690
+ className={ clsx(
691
+ 'block-editor-global-styles-background-panel__inspector-media-replace-container',
692
+ {
693
+ 'is-open': isDropDownOpen,
694
+ }
695
+ ) }
696
+ >
697
+ { shouldShowBackgroundImageControls ? (
698
+ <BackgroundControlsPanel
699
+ label={ title }
700
+ filename={ title }
701
+ url={ url }
702
+ onToggle={ setIsDropDownOpen }
703
+ hasImageValue={ hasImageValue }
704
+ >
705
+ <VStack spacing={ 3 } className="single-column">
706
+ <BackgroundImageControls
707
+ onChange={ onChange }
708
+ style={ value }
709
+ inheritedValue={ resolvedInheritedValue }
710
+ displayInPanel
711
+ onResetImage={ () => {
712
+ setIsDropDownOpen( false );
713
+ resetBackground();
714
+ } }
715
+ onRemoveImage={ () => setIsDropDownOpen( false ) }
716
+ defaultValues={ defaultValues }
717
+ />
718
+ <BackgroundSizeControls
719
+ onChange={ onChange }
720
+ style={ value }
721
+ defaultValues={ defaultValues }
722
+ inheritedValue={ resolvedInheritedValue }
723
+ />
724
+ </VStack>
725
+ </BackgroundControlsPanel>
726
+ ) : (
727
+ <BackgroundImageControls
728
+ onChange={ onChange }
729
+ style={ value }
730
+ inheritedValue={ resolvedInheritedValue }
731
+ defaultValues={ defaultValues }
732
+ onResetImage={ () => {
733
+ setIsDropDownOpen( false );
734
+ resetBackground();
735
+ } }
736
+ onRemoveImage={ () => setIsDropDownOpen( false ) }
737
+ />
738
+ ) }
739
+ </div>
740
+ );
741
+ }