@wordpress/block-editor 9.3.0 → 9.4.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 (171) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/components/block-pattern-setup/index.js +3 -9
  3. package/build/components/block-pattern-setup/index.js.map +1 -1
  4. package/build/components/block-pattern-setup/setup-toolbar.js +3 -8
  5. package/build/components/block-pattern-setup/setup-toolbar.js.map +1 -1
  6. package/build/components/block-preview/auto.js +21 -5
  7. package/build/components/block-preview/auto.js.map +1 -1
  8. package/build/components/block-settings-menu/block-edit-visually-button.js +70 -0
  9. package/build/components/block-settings-menu/block-edit-visually-button.js.map +1 -0
  10. package/build/components/block-settings-menu/block-settings-dropdown.js +1 -1
  11. package/build/components/block-settings-menu/block-settings-dropdown.js.map +1 -1
  12. package/build/components/block-settings-menu/index.js +6 -2
  13. package/build/components/block-settings-menu/index.js.map +1 -1
  14. package/build/components/block-settings-menu-controls/index.js +4 -1
  15. package/build/components/block-settings-menu-controls/index.js.map +1 -1
  16. package/build/components/block-title/use-block-display-title.js +3 -10
  17. package/build/components/block-title/use-block-display-title.js.map +1 -1
  18. package/build/components/colors-gradients/dropdown.js +2 -1
  19. package/build/components/colors-gradients/dropdown.js.map +1 -1
  20. package/build/components/duotone/components.js +145 -0
  21. package/build/components/duotone/components.js.map +1 -0
  22. package/build/components/duotone/index.js +40 -0
  23. package/build/components/duotone/index.js.map +1 -0
  24. package/build/components/duotone/utils.js +38 -0
  25. package/build/components/duotone/utils.js.map +1 -0
  26. package/build/components/duotone-control/index.js +17 -5
  27. package/build/components/duotone-control/index.js.map +1 -1
  28. package/build/components/index.js +14 -0
  29. package/build/components/index.js.map +1 -1
  30. package/build/components/inserter/index.js +3 -3
  31. package/build/components/inserter/index.js.map +1 -1
  32. package/build/components/media-placeholder/index.js +1 -0
  33. package/build/components/media-placeholder/index.js.map +1 -1
  34. package/build/components/media-placeholder/index.native.js +4 -4
  35. package/build/components/media-placeholder/index.native.js.map +1 -1
  36. package/build/components/media-replace-flow/index.js +3 -7
  37. package/build/components/media-replace-flow/index.js.map +1 -1
  38. package/build/components/publish-date-time-picker/index.js +3 -0
  39. package/build/components/publish-date-time-picker/index.js.map +1 -1
  40. package/build/components/rich-text/use-input-rules.js +4 -13
  41. package/build/components/rich-text/use-input-rules.js.map +1 -1
  42. package/build/components/rich-text/use-paste-handler.js +20 -5
  43. package/build/components/rich-text/use-paste-handler.js.map +1 -1
  44. package/build/elements/index.js +11 -3
  45. package/build/elements/index.js.map +1 -1
  46. package/build/hooks/aria-label.js +71 -0
  47. package/build/hooks/aria-label.js.map +1 -0
  48. package/build/hooks/duotone.js +33 -160
  49. package/build/hooks/duotone.js.map +1 -1
  50. package/build/hooks/index.js +3 -7
  51. package/build/hooks/index.js.map +1 -1
  52. package/build/hooks/layout.js +6 -4
  53. package/build/hooks/layout.js.map +1 -1
  54. package/build/index.js +0 -7
  55. package/build/index.js.map +1 -1
  56. package/build/layouts/flex.js +2 -2
  57. package/build/layouts/flex.js.map +1 -1
  58. package/build/store/actions.js +10 -14
  59. package/build/store/actions.js.map +1 -1
  60. package/build/store/reducer.js +18 -9
  61. package/build/store/reducer.js.map +1 -1
  62. package/build/utils/selection.js +34 -0
  63. package/build/utils/selection.js.map +1 -0
  64. package/build-module/components/block-pattern-setup/index.js +3 -9
  65. package/build-module/components/block-pattern-setup/index.js.map +1 -1
  66. package/build-module/components/block-pattern-setup/setup-toolbar.js +3 -8
  67. package/build-module/components/block-pattern-setup/setup-toolbar.js.map +1 -1
  68. package/build-module/components/block-preview/auto.js +20 -5
  69. package/build-module/components/block-preview/auto.js.map +1 -1
  70. package/build-module/components/block-settings-menu/block-edit-visually-button.js +56 -0
  71. package/build-module/components/block-settings-menu/block-edit-visually-button.js.map +1 -0
  72. package/build-module/components/block-settings-menu/block-settings-dropdown.js +2 -4
  73. package/build-module/components/block-settings-menu/block-settings-dropdown.js.map +1 -1
  74. package/build-module/components/block-settings-menu/index.js +6 -3
  75. package/build-module/components/block-settings-menu/index.js.map +1 -1
  76. package/build-module/components/block-settings-menu-controls/index.js +5 -2
  77. package/build-module/components/block-settings-menu-controls/index.js.map +1 -1
  78. package/build-module/components/block-title/use-block-display-title.js +3 -9
  79. package/build-module/components/block-title/use-block-display-title.js.map +1 -1
  80. package/build-module/components/colors-gradients/dropdown.js +2 -1
  81. package/build-module/components/colors-gradients/dropdown.js.map +1 -1
  82. package/build-module/components/duotone/components.js +130 -0
  83. package/build-module/components/duotone/components.js.map +1 -0
  84. package/build-module/components/duotone/index.js +3 -0
  85. package/build-module/components/duotone/index.js.map +1 -0
  86. package/build-module/components/duotone/utils.js +30 -0
  87. package/build-module/components/duotone/utils.js.map +1 -0
  88. package/build-module/components/duotone-control/index.js +18 -6
  89. package/build-module/components/duotone-control/index.js.map +1 -1
  90. package/build-module/components/index.js +1 -0
  91. package/build-module/components/index.js.map +1 -1
  92. package/build-module/components/inserter/index.js +3 -2
  93. package/build-module/components/inserter/index.js.map +1 -1
  94. package/build-module/components/media-placeholder/index.js +1 -0
  95. package/build-module/components/media-placeholder/index.js.map +1 -1
  96. package/build-module/components/media-placeholder/index.native.js +5 -3
  97. package/build-module/components/media-placeholder/index.native.js.map +1 -1
  98. package/build-module/components/media-replace-flow/index.js +3 -6
  99. package/build-module/components/media-replace-flow/index.js.map +1 -1
  100. package/build-module/components/publish-date-time-picker/index.js +2 -0
  101. package/build-module/components/publish-date-time-picker/index.js.map +1 -1
  102. package/build-module/components/rich-text/use-input-rules.js +3 -11
  103. package/build-module/components/rich-text/use-input-rules.js.map +1 -1
  104. package/build-module/components/rich-text/use-paste-handler.js +20 -5
  105. package/build-module/components/rich-text/use-paste-handler.js.map +1 -1
  106. package/build-module/elements/index.js +7 -1
  107. package/build-module/elements/index.js.map +1 -1
  108. package/build-module/hooks/aria-label.js +59 -0
  109. package/build-module/hooks/aria-label.js.map +1 -0
  110. package/build-module/hooks/duotone.js +22 -140
  111. package/build-module/hooks/duotone.js.map +1 -1
  112. package/build-module/hooks/index.js +1 -1
  113. package/build-module/hooks/index.js.map +1 -1
  114. package/build-module/hooks/layout.js +6 -4
  115. package/build-module/hooks/layout.js.map +1 -1
  116. package/build-module/index.js +1 -1
  117. package/build-module/index.js.map +1 -1
  118. package/build-module/layouts/flex.js +2 -2
  119. package/build-module/layouts/flex.js.map +1 -1
  120. package/build-module/store/actions.js +6 -11
  121. package/build-module/store/actions.js.map +1 -1
  122. package/build-module/store/reducer.js +19 -10
  123. package/build-module/store/reducer.js.map +1 -1
  124. package/build-module/utils/selection.js +24 -0
  125. package/build-module/utils/selection.js.map +1 -0
  126. package/build-style/style-rtl.css +5 -1
  127. package/build-style/style.css +5 -1
  128. package/package.json +28 -28
  129. package/src/components/block-draggable/test/helpers.native.js +3 -3
  130. package/src/components/block-list/style.scss +1 -1
  131. package/src/components/block-pattern-setup/index.js +2 -10
  132. package/src/components/block-pattern-setup/setup-toolbar.js +2 -9
  133. package/src/components/block-preview/auto.js +17 -3
  134. package/src/components/block-settings-menu/block-edit-visually-button.js +52 -0
  135. package/src/components/block-settings-menu/block-settings-dropdown.js +3 -2
  136. package/src/components/block-settings-menu/index.js +15 -11
  137. package/src/components/block-settings-menu-controls/index.js +3 -2
  138. package/src/components/block-title/use-block-display-title.js +9 -7
  139. package/src/components/colors-gradients/dropdown.js +1 -0
  140. package/src/components/duotone/components.js +133 -0
  141. package/src/components/duotone/index.js +7 -0
  142. package/src/components/duotone/utils.js +25 -0
  143. package/src/components/duotone-control/index.js +12 -7
  144. package/src/components/duotone-control/style.scss +5 -0
  145. package/src/components/index.js +1 -0
  146. package/src/components/inserter/index.js +3 -5
  147. package/src/components/link-control/test/fixtures/index.js +3 -4
  148. package/src/components/link-control/test/index.js +58 -69
  149. package/src/components/media-placeholder/index.js +1 -0
  150. package/src/components/media-placeholder/index.native.js +9 -5
  151. package/src/components/media-replace-flow/index.js +2 -8
  152. package/src/components/media-upload/README.md +8 -0
  153. package/src/components/publish-date-time-picker/index.js +2 -0
  154. package/src/components/responsive-block-control/README.md +3 -1
  155. package/src/components/responsive-block-control/test/index.js +1 -2
  156. package/src/components/rich-text/use-input-rules.js +6 -15
  157. package/src/components/rich-text/use-paste-handler.js +17 -5
  158. package/src/elements/index.js +8 -1
  159. package/src/elements/test/index.js +18 -0
  160. package/src/hooks/aria-label.js +67 -0
  161. package/src/hooks/duotone.js +18 -139
  162. package/src/hooks/index.js +1 -1
  163. package/src/hooks/layout.js +20 -9
  164. package/src/index.js +0 -1
  165. package/src/layouts/flex.js +2 -2
  166. package/src/store/actions.js +8 -21
  167. package/src/store/reducer.js +21 -9
  168. package/src/store/test/reducer.js +138 -10
  169. package/src/store/test/selectors.js +3 -6
  170. package/src/utils/selection.js +26 -0
  171. package/src/utils/test/selection.js +39 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/block-editor",
3
- "version": "9.3.0",
3
+ "version": "9.4.0",
4
4
  "description": "Generic block editor.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -33,32 +33,32 @@
33
33
  "dependencies": {
34
34
  "@babel/runtime": "^7.16.0",
35
35
  "@react-spring/web": "^9.4.5",
36
- "@wordpress/a11y": "^3.11.0",
37
- "@wordpress/api-fetch": "^6.8.0",
38
- "@wordpress/blob": "^3.11.0",
39
- "@wordpress/blocks": "^11.10.0",
40
- "@wordpress/components": "^19.13.0",
41
- "@wordpress/compose": "^5.9.0",
42
- "@wordpress/data": "^6.11.0",
43
- "@wordpress/date": "^4.11.0",
44
- "@wordpress/deprecated": "^3.11.0",
45
- "@wordpress/dom": "^3.11.0",
46
- "@wordpress/element": "^4.9.0",
47
- "@wordpress/hooks": "^3.11.0",
48
- "@wordpress/html-entities": "^3.11.0",
49
- "@wordpress/i18n": "^4.11.0",
50
- "@wordpress/icons": "^9.2.0",
51
- "@wordpress/is-shallow-equal": "^4.11.0",
52
- "@wordpress/keyboard-shortcuts": "^3.9.0",
53
- "@wordpress/keycodes": "^3.11.0",
54
- "@wordpress/notices": "^3.11.0",
55
- "@wordpress/rich-text": "^5.9.0",
56
- "@wordpress/shortcode": "^3.11.0",
57
- "@wordpress/style-engine": "^0.10.0",
58
- "@wordpress/token-list": "^2.11.0",
59
- "@wordpress/url": "^3.12.0",
60
- "@wordpress/warning": "^2.11.0",
61
- "@wordpress/wordcount": "^3.11.0",
36
+ "@wordpress/a11y": "^3.12.0",
37
+ "@wordpress/api-fetch": "^6.9.0",
38
+ "@wordpress/blob": "^3.12.0",
39
+ "@wordpress/blocks": "^11.11.0",
40
+ "@wordpress/components": "^19.14.0",
41
+ "@wordpress/compose": "^5.10.0",
42
+ "@wordpress/data": "^6.12.0",
43
+ "@wordpress/date": "^4.12.0",
44
+ "@wordpress/deprecated": "^3.12.0",
45
+ "@wordpress/dom": "^3.12.0",
46
+ "@wordpress/element": "^4.10.0",
47
+ "@wordpress/hooks": "^3.12.0",
48
+ "@wordpress/html-entities": "^3.12.0",
49
+ "@wordpress/i18n": "^4.12.0",
50
+ "@wordpress/icons": "^9.3.0",
51
+ "@wordpress/is-shallow-equal": "^4.12.0",
52
+ "@wordpress/keyboard-shortcuts": "^3.10.0",
53
+ "@wordpress/keycodes": "^3.12.0",
54
+ "@wordpress/notices": "^3.12.0",
55
+ "@wordpress/rich-text": "^5.10.0",
56
+ "@wordpress/shortcode": "^3.12.0",
57
+ "@wordpress/style-engine": "^0.11.0",
58
+ "@wordpress/token-list": "^2.12.0",
59
+ "@wordpress/url": "^3.13.0",
60
+ "@wordpress/warning": "^2.12.0",
61
+ "@wordpress/wordcount": "^3.12.0",
62
62
  "classnames": "^2.3.1",
63
63
  "colord": "^2.7.0",
64
64
  "diff": "^4.0.2",
@@ -77,5 +77,5 @@
77
77
  "publishConfig": {
78
78
  "access": "public"
79
79
  },
80
- "gitHead": "48d5f37dfb52d2e77c8eeb662f9874cf141b8c6b"
80
+ "gitHead": "a80eeb62ec7cb1418b9915c277e084a29d6665e3"
81
81
  }
@@ -7,7 +7,7 @@ import {
7
7
  initializeEditor,
8
8
  waitForStoreResolvers,
9
9
  within,
10
- advanceAnimationByFrame,
10
+ advanceAnimationByFrames,
11
11
  } from 'test/helpers';
12
12
  import { fireGestureHandler } from 'react-native-gesture-handler/jest-utils';
13
13
  import { State } from 'react-native-gesture-handler';
@@ -135,7 +135,7 @@ export const fireLongPress = (
135
135
  }
136
136
  // Advance timers one frame to ensure that shared values
137
137
  // are updated and trigger animation reactions.
138
- act( () => advanceAnimationByFrame( 1 ) );
138
+ act( () => advanceAnimationByFrames( 1 ) );
139
139
  };
140
140
 
141
141
  /**
@@ -162,7 +162,7 @@ export const firePanGesture = (
162
162
  ] );
163
163
  // Advance timers one frame to ensure that shared values
164
164
  // are updated and trigger animation reactions.
165
- act( () => advanceAnimationByFrame( 1 ) );
165
+ act( () => advanceAnimationByFrames( 1 ) );
166
166
  };
167
167
 
168
168
  /**
@@ -293,7 +293,7 @@
293
293
 
294
294
  // Spotlight mode. Fade out blocks unless they contain a selected block.
295
295
  .is-focus-mode .block-editor-block-list__block:not(.has-child-selected) {
296
- opacity: 0.5;
296
+ opacity: 0.2;
297
297
  transition: opacity 0.1s linear;
298
298
  @include reduce-motion("transition");
299
299
 
@@ -145,19 +145,17 @@ const BlockPatternSetup = ( {
145
145
  clientId,
146
146
  blockName,
147
147
  filterPatternsFn,
148
- startBlankComponent = null,
149
148
  onBlockPatternSelect,
150
149
  } ) => {
151
150
  const [ viewMode, setViewMode ] = useState( VIEWMODES.carousel );
152
151
  const [ activeSlide, setActiveSlide ] = useState( 0 );
153
- const [ showBlank, setShowBlank ] = useState( false );
154
152
  const { replaceBlock } = useDispatch( blockEditorStore );
155
153
  const patterns = usePatternsSetup( clientId, blockName, filterPatternsFn );
156
154
  const [ contentResizeListener, { height: contentHeight } ] =
157
155
  useResizeObserver();
158
156
 
159
- if ( ! patterns?.length || showBlank ) {
160
- return startBlankComponent;
157
+ if ( ! patterns?.length ) {
158
+ return null;
161
159
  }
162
160
 
163
161
  const onBlockPatternSelectDefault = ( blocks ) => {
@@ -166,11 +164,6 @@ const BlockPatternSetup = ( {
166
164
  };
167
165
  const onPatternSelectCallback =
168
166
  onBlockPatternSelect || onBlockPatternSelectDefault;
169
- const onStartBlank = startBlankComponent
170
- ? () => {
171
- setShowBlank( true );
172
- }
173
- : undefined;
174
167
  return (
175
168
  <>
176
169
  { contentResizeListener }
@@ -200,7 +193,6 @@ const BlockPatternSetup = ( {
200
193
  patterns[ activeSlide ].blocks
201
194
  );
202
195
  } }
203
- onStartBlank={ onStartBlank }
204
196
  />
205
197
  </div>
206
198
  </>
@@ -15,11 +15,8 @@ import {
15
15
  */
16
16
  import { VIEWMODES } from './constants';
17
17
 
18
- const Actions = ( { onStartBlank, onBlockPatternSelect } ) => (
18
+ const Actions = ( { onBlockPatternSelect } ) => (
19
19
  <div className="block-editor-block-pattern-setup__actions">
20
- { onStartBlank && (
21
- <Button onClick={ onStartBlank }>{ __( 'Start blank' ) }</Button>
22
- ) }
23
20
  <Button variant="primary" onClick={ onBlockPatternSelect }>
24
21
  { __( 'Choose' ) }
25
22
  </Button>
@@ -56,7 +53,6 @@ const SetupToolbar = ( {
56
53
  activeSlide,
57
54
  totalSlides,
58
55
  onBlockPatternSelect,
59
- onStartBlank,
60
56
  } ) => {
61
57
  const isCarouselView = viewMode === VIEWMODES.carousel;
62
58
  const displayControls = (
@@ -87,10 +83,7 @@ const SetupToolbar = ( {
87
83
  ) }
88
84
  { displayControls }
89
85
  { isCarouselView && (
90
- <Actions
91
- onBlockPatternSelect={ onBlockPatternSelect }
92
- onStartBlank={ onStartBlank }
93
- />
86
+ <Actions onBlockPatternSelect={ onBlockPatternSelect } />
94
87
  ) }
95
88
  </div>
96
89
  );
@@ -12,6 +12,7 @@ import { useMemo } from '@wordpress/element';
12
12
  import BlockList from '../block-list';
13
13
  import Iframe from '../iframe';
14
14
  import EditorStyles from '../editor-styles';
15
+ import { __unstablePresetDuotoneFilter as PresetDuotoneFilter } from '../../components/duotone';
15
16
  import { store } from '../../store';
16
17
 
17
18
  // This is used to avoid rendering the block list if the sizes change.
@@ -28,11 +29,12 @@ function AutoBlockPreview( {
28
29
  useResizeObserver();
29
30
  const [ contentResizeListener, { height: contentHeight } ] =
30
31
  useResizeObserver();
31
- const { styles, assets } = useSelect( ( select ) => {
32
+ const { styles, assets, duotone } = useSelect( ( select ) => {
32
33
  const settings = select( store ).getSettings();
33
34
  return {
34
35
  styles: settings.styles,
35
36
  assets: settings.__unstableResolvedAssets,
37
+ duotone: settings.__experimentalFeatures?.color?.duotone,
36
38
  };
37
39
  }, [] );
38
40
 
@@ -51,11 +53,14 @@ function AutoBlockPreview( {
51
53
  return styles;
52
54
  }, [ styles ] );
53
55
 
56
+ const svgFilters = useMemo( () => {
57
+ return [ ...( duotone?.default ?? [] ), ...( duotone?.theme ?? [] ) ];
58
+ }, [ duotone ] );
59
+
54
60
  // Initialize on render instead of module top level, to avoid circular dependency issues.
55
61
  MemoizedBlockList = MemoizedBlockList || pure( BlockList );
56
62
 
57
63
  const scale = containerWidth / viewportWidth;
58
-
59
64
  return (
60
65
  <div className="block-editor-block-preview__container">
61
66
  { containerResizeListener }
@@ -100,12 +105,21 @@ function AutoBlockPreview( {
100
105
  // See: https://github.com/WordPress/gutenberg/pull/38175.
101
106
  maxHeight: MAX_HEIGHT,
102
107
  minHeight:
103
- scale < 1 && __experimentalMinHeight
108
+ scale !== 0 && scale < 1 && __experimentalMinHeight
104
109
  ? __experimentalMinHeight / scale
105
110
  : __experimentalMinHeight,
106
111
  } }
107
112
  >
108
113
  { contentResizeListener }
114
+ {
115
+ /* Filters need to be rendered before children to avoid Safari rendering issues. */
116
+ svgFilters.map( ( preset ) => (
117
+ <PresetDuotoneFilter
118
+ preset={ preset }
119
+ key={ preset.slug }
120
+ />
121
+ ) )
122
+ }
109
123
  <MemoizedBlockList renderAppender={ false } />
110
124
  </Iframe>
111
125
  </Disabled>
@@ -0,0 +1,52 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { rawHandler, getBlockContent } from '@wordpress/blocks';
5
+ import { ToolbarButton, ToolbarGroup } from '@wordpress/components';
6
+ import { __ } from '@wordpress/i18n';
7
+ import { useSelect, useDispatch } from '@wordpress/data';
8
+ import { useCallback } from '@wordpress/element';
9
+
10
+ /**
11
+ * Internal dependencies
12
+ */
13
+ import { store as blockEditorStore } from '../../store';
14
+
15
+ export default function BlockEditVisuallyButton( { clientIds, ...props } ) {
16
+ const { block, shouldRender } = useSelect(
17
+ ( select ) => {
18
+ const firstBlockClientId = clientIds[ 0 ];
19
+ const { isBlockMultiSelected, getBlockMode, getBlock } =
20
+ select( blockEditorStore );
21
+ const isSingleSelected =
22
+ ! isBlockMultiSelected( firstBlockClientId );
23
+ const isHtmlMode = getBlockMode( firstBlockClientId ) === 'html';
24
+
25
+ return {
26
+ block: getBlock( firstBlockClientId ),
27
+ shouldRender: isSingleSelected && isHtmlMode,
28
+ };
29
+ },
30
+ [ clientIds[ 0 ] ]
31
+ );
32
+
33
+ const { replaceBlocks } = useDispatch( blockEditorStore );
34
+ const onClick = useCallback( () => {
35
+ replaceBlocks(
36
+ block.clientId,
37
+ rawHandler( { HTML: getBlockContent( block ) } )
38
+ );
39
+ }, [ block ] );
40
+
41
+ if ( ! shouldRender ) {
42
+ return null;
43
+ }
44
+
45
+ return (
46
+ <ToolbarGroup>
47
+ <ToolbarButton onClick={ onClick } { ...props }>
48
+ { __( 'Edit visually' ) }
49
+ </ToolbarButton>
50
+ </ToolbarGroup>
51
+ );
52
+ }
@@ -16,7 +16,7 @@ import {
16
16
  useCallback,
17
17
  useRef,
18
18
  } from '@wordpress/element';
19
- import { __, _n, sprintf } from '@wordpress/i18n';
19
+ import { __, sprintf } from '@wordpress/i18n';
20
20
  import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
21
21
  import { useCopyToClipboard } from '@wordpress/compose';
22
22
 
@@ -42,7 +42,8 @@ const POPOVER_PROPS = {
42
42
 
43
43
  function CopyMenuItem( { blocks, onCopy } ) {
44
44
  const ref = useCopyToClipboard( () => serialize( blocks ), onCopy );
45
- const copyMenuItemLabel = _n( 'Copy block', 'Copy blocks', blocks.length );
45
+ const copyMenuItemLabel =
46
+ blocks.length > 1 ? __( 'Copy blocks' ) : __( 'Copy block' );
46
47
  return <MenuItem ref={ ref }>{ copyMenuItemLabel }</MenuItem>;
47
48
  }
48
49
 
@@ -7,20 +7,24 @@ import { ToolbarGroup, ToolbarItem } from '@wordpress/components';
7
7
  * Internal dependencies
8
8
  */
9
9
  import BlockSettingsDropdown from './block-settings-dropdown';
10
+ import BlockEditVisuallyButton from './block-edit-visually-button';
10
11
 
11
12
  export function BlockSettingsMenu( { clientIds, ...props } ) {
12
13
  return (
13
- <ToolbarGroup>
14
- <ToolbarItem>
15
- { ( toggleProps ) => (
16
- <BlockSettingsDropdown
17
- clientIds={ clientIds }
18
- toggleProps={ toggleProps }
19
- { ...props }
20
- />
21
- ) }
22
- </ToolbarItem>
23
- </ToolbarGroup>
14
+ <>
15
+ <BlockEditVisuallyButton clientIds={ clientIds } { ...props } />
16
+ <ToolbarGroup>
17
+ <ToolbarItem>
18
+ { ( toggleProps ) => (
19
+ <BlockSettingsDropdown
20
+ clientIds={ clientIds }
21
+ toggleProps={ toggleProps }
22
+ { ...props }
23
+ />
24
+ ) }
25
+ </ToolbarItem>
26
+ </ToolbarGroup>
27
+ </>
24
28
  );
25
29
  }
26
30
 
@@ -20,7 +20,7 @@ import {
20
20
  useConvertToGroupButtonProps,
21
21
  ConvertToGroupButton,
22
22
  } from '../convert-to-group-buttons';
23
- import { BlockLockMenuItem } from '../block-lock';
23
+ import { BlockLockMenuItem, useBlockLock } from '../block-lock';
24
24
  import { store as blockEditorStore } from '../../store';
25
25
 
26
26
  const { Fill, Slot } = createSlotFill( 'BlockSettingsMenuControls' );
@@ -47,7 +47,8 @@ const BlockSettingsMenuControlsSlot = ( { fillProps, clientIds = null } ) => {
47
47
  [ clientIds ]
48
48
  );
49
49
 
50
- const showLockButton = selectedClientIds.length === 1;
50
+ const { canLock } = useBlockLock( selectedClientIds[ 0 ] );
51
+ const showLockButton = selectedClientIds.length === 1 && canLock;
51
52
 
52
53
  // Check if current selection of blocks is Groupable or Ungroupable
53
54
  // and pass this props down to ConvertToGroupButton.
@@ -1,8 +1,3 @@
1
- /**
2
- * External dependencies
3
- */
4
- import { truncate } from 'lodash';
5
-
6
1
  /**
7
2
  * WordPress dependencies
8
3
  */
@@ -78,8 +73,15 @@ export default function useBlockDisplayTitle( clientId, maximumLength ) {
78
73
  const blockTitle =
79
74
  label && label !== blockType.title ? label : blockInformation.title;
80
75
 
81
- if ( maximumLength && maximumLength > 0 ) {
82
- return truncate( blockTitle, { length: maximumLength } );
76
+ if (
77
+ maximumLength &&
78
+ maximumLength > 0 &&
79
+ blockTitle.length > maximumLength
80
+ ) {
81
+ const omission = '...';
82
+ return (
83
+ blockTitle.slice( 0, maximumLength - omission.length ) + omission
84
+ );
83
85
  }
84
86
 
85
87
  return blockTitle;
@@ -114,6 +114,7 @@ export default function ColorGradientSettingsDropdown( {
114
114
  popoverProps = {
115
115
  placement: 'left-start',
116
116
  offset: 36,
117
+ __unstableShift: true,
117
118
  };
118
119
  }
119
120
 
@@ -0,0 +1,133 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import { SVG } from '@wordpress/components';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import { __unstableGetValuesFromColors as getValuesFromColors } from './index';
10
+
11
+ /**
12
+ * SVG and stylesheet needed for rendering the duotone filter.
13
+ *
14
+ * @param {Object} props Duotone props.
15
+ * @param {string} props.selector Selector to apply the filter to.
16
+ * @param {string} props.id Unique id for this duotone filter.
17
+ *
18
+ * @return {WPElement} Duotone element.
19
+ */
20
+ export function DuotoneStylesheet( { selector, id } ) {
21
+ const css = `
22
+ ${ selector } {
23
+ filter: url( #${ id } );
24
+ }
25
+ `;
26
+ return <style>{ css }</style>;
27
+ }
28
+
29
+ /**
30
+ * Stylesheet for disabling a global styles duotone filter.
31
+ *
32
+ * @param {Object} props Duotone props.
33
+ * @param {string} props.selector Selector to disable the filter for.
34
+ *
35
+ * @return {WPElement} Filter none style element.
36
+ */
37
+ export function DuotoneUnsetStylesheet( { selector } ) {
38
+ const css = `
39
+ ${ selector } {
40
+ filter: none;
41
+ }
42
+ `;
43
+ return <style>{ css }</style>;
44
+ }
45
+
46
+ /**
47
+ * The SVG part of the duotone filter.
48
+ *
49
+ * @param {Object} props Duotone props.
50
+ * @param {string} props.id Unique id for this duotone filter.
51
+ * @param {string[]} props.colors Color strings from dark to light.
52
+ *
53
+ * @return {WPElement} Duotone SVG.
54
+ */
55
+ export function DuotoneFilter( { id, colors } ) {
56
+ const values = getValuesFromColors( colors );
57
+ return (
58
+ <SVG
59
+ xmlnsXlink="http://www.w3.org/1999/xlink"
60
+ viewBox="0 0 0 0"
61
+ width="0"
62
+ height="0"
63
+ focusable="false"
64
+ role="none"
65
+ style={ {
66
+ visibility: 'hidden',
67
+ position: 'absolute',
68
+ left: '-9999px',
69
+ overflow: 'hidden',
70
+ } }
71
+ >
72
+ <defs>
73
+ <filter id={ id }>
74
+ <feColorMatrix
75
+ // Use sRGB instead of linearRGB so transparency looks correct.
76
+ colorInterpolationFilters="sRGB"
77
+ type="matrix"
78
+ // Use perceptual brightness to convert to grayscale.
79
+ values="
80
+ .299 .587 .114 0 0
81
+ .299 .587 .114 0 0
82
+ .299 .587 .114 0 0
83
+ .299 .587 .114 0 0
84
+ "
85
+ />
86
+ <feComponentTransfer
87
+ // Use sRGB instead of linearRGB to be consistent with how CSS gradients work.
88
+ colorInterpolationFilters="sRGB"
89
+ >
90
+ <feFuncR
91
+ type="table"
92
+ tableValues={ values.r.join( ' ' ) }
93
+ />
94
+ <feFuncG
95
+ type="table"
96
+ tableValues={ values.g.join( ' ' ) }
97
+ />
98
+ <feFuncB
99
+ type="table"
100
+ tableValues={ values.b.join( ' ' ) }
101
+ />
102
+ <feFuncA
103
+ type="table"
104
+ tableValues={ values.a.join( ' ' ) }
105
+ />
106
+ </feComponentTransfer>
107
+ <feComposite
108
+ // Re-mask the image with the original transparency since the feColorMatrix above loses that information.
109
+ in2="SourceGraphic"
110
+ operator="in"
111
+ />
112
+ </filter>
113
+ </defs>
114
+ </SVG>
115
+ );
116
+ }
117
+
118
+ /**
119
+ * SVG from a duotone preset
120
+ *
121
+ * @param {Object} props Duotone props.
122
+ * @param {Object} props.preset Duotone preset settings.
123
+ *
124
+ * @return {WPElement} Duotone element.
125
+ */
126
+ export function PresetDuotoneFilter( { preset } ) {
127
+ return (
128
+ <DuotoneFilter
129
+ id={ `wp-duotone-${ preset.slug }` }
130
+ colors={ preset.colors }
131
+ />
132
+ );
133
+ }
@@ -0,0 +1,7 @@
1
+ export { getValuesFromColors as __unstableGetValuesFromColors } from './utils';
2
+ export {
3
+ DuotoneFilter as __unstableDuotoneFilter,
4
+ PresetDuotoneFilter as __unstablePresetDuotoneFilter,
5
+ DuotoneStylesheet as __unstableDuotoneStylesheet,
6
+ DuotoneUnsetStylesheet as __unstableDuotoneUnsetStylesheet,
7
+ } from './components';
@@ -0,0 +1,25 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { colord } from 'colord';
5
+
6
+ /**
7
+ * Convert a list of colors to an object of R, G, and B values.
8
+ *
9
+ * @param {string[]} colors Array of RBG color strings.
10
+ *
11
+ * @return {Object} R, G, and B values.
12
+ */
13
+ export function getValuesFromColors( colors = [] ) {
14
+ const values = { r: [], g: [], b: [], a: [] };
15
+
16
+ colors.forEach( ( color ) => {
17
+ const rgbColor = colord( color ).toRgb();
18
+ values.r.push( rgbColor.r / 255 );
19
+ values.g.push( rgbColor.g / 255 );
20
+ values.b.push( rgbColor.b / 255 );
21
+ values.a.push( rgbColor.a );
22
+ } );
23
+
24
+ return values;
25
+ }
@@ -2,6 +2,7 @@
2
2
  * WordPress dependencies
3
3
  */
4
4
  import {
5
+ ColorIndicator,
5
6
  Dropdown,
6
7
  DuotonePicker,
7
8
  DuotoneSwatch,
@@ -20,6 +21,16 @@ function DuotoneControl( {
20
21
  value,
21
22
  onChange,
22
23
  } ) {
24
+ let toolbarIcon;
25
+ if ( value === 'unset' ) {
26
+ toolbarIcon = (
27
+ <ColorIndicator className="block-editor-duotone-control__unset-indicator" />
28
+ );
29
+ } else if ( value ) {
30
+ toolbarIcon = <DuotoneSwatch values={ value } />;
31
+ } else {
32
+ toolbarIcon = <Icon icon={ filter } />;
33
+ }
23
34
  return (
24
35
  <Dropdown
25
36
  popoverProps={ {
@@ -42,13 +53,7 @@ function DuotoneControl( {
42
53
  aria-expanded={ isOpen }
43
54
  onKeyDown={ openOnArrowDown }
44
55
  label={ __( 'Apply duotone filter' ) }
45
- icon={
46
- value ? (
47
- <DuotoneSwatch values={ value } />
48
- ) : (
49
- <Icon icon={ filter } />
50
- )
51
- }
56
+ icon={ toolbarIcon }
52
57
  />
53
58
  );
54
59
  } }
@@ -34,3 +34,8 @@ $swatch-columns: math.floor(math.div($popover-width + $swatch-gap - 2 * $popover
34
34
  margin: $grid-unit-20 0;
35
35
  font-size: $helptext-font-size;
36
36
  }
37
+
38
+ .block-editor-duotone-control__unset-indicator {
39
+ // Show a diagonal line (crossed out) for empty swatches.
40
+ background: linear-gradient(-45deg, transparent 48%, $gray-300 48%, $gray-300 52%, transparent 52%);
41
+ }
@@ -5,6 +5,7 @@
5
5
  export * from './colors';
6
6
  export * from './gradients';
7
7
  export * from './font-sizes';
8
+ export * from './duotone';
8
9
  export { AlignmentControl, AlignmentToolbar } from './alignment-control';
9
10
  export { default as Autocomplete } from './autocomplete';
10
11
  export {
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { size } from 'lodash';
5
4
  import classnames from 'classnames';
6
5
 
7
6
  /**
@@ -233,10 +232,9 @@ export default compose( [
233
232
  const settings = getSettings();
234
233
 
235
234
  const hasSingleBlockType =
236
- size( allowedBlocks ) === 1 &&
237
- size(
238
- getBlockVariations( allowedBlocks[ 0 ].name, 'inserter' )
239
- ) === 0;
235
+ allowedBlocks?.length === 1 &&
236
+ getBlockVariations( allowedBlocks[ 0 ].name, 'inserter' )
237
+ ?.length === 0;
240
238
 
241
239
  let allowedBlockType = false;
242
240
  if ( hasSingleBlockType ) {
@@ -1,7 +1,6 @@
1
- /**
2
- * External dependencies
3
- */
4
- import { uniqueId } from 'lodash';
1
+ let uniqueIdCounter = 1;
2
+
3
+ export const uniqueId = () => uniqueIdCounter++;
5
4
 
6
5
  export const fauxEntitySuggestions = [
7
6
  {