@wordpress/block-editor 8.5.2 → 8.6.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 (186) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/components/block-list/block.native.js +3 -1
  3. package/build/components/block-list/block.native.js.map +1 -1
  4. package/build/components/block-mover/button.js +4 -4
  5. package/build/components/block-mover/button.js.map +1 -1
  6. package/build/components/block-mover/index.js +39 -65
  7. package/build/components/block-mover/index.js.map +1 -1
  8. package/build/components/block-popover/inbetween.js +183 -0
  9. package/build/components/block-popover/inbetween.js.map +1 -0
  10. package/build/components/block-popover/index.js +82 -0
  11. package/build/components/block-popover/index.js.map +1 -0
  12. package/build/components/{block-tools → block-popover}/use-popover-scroll.js +4 -1
  13. package/build/components/block-popover/use-popover-scroll.js.map +1 -0
  14. package/build/components/block-styles/index.js +1 -10
  15. package/build/components/block-styles/index.js.map +1 -1
  16. package/build/components/block-tools/back-compat.js +2 -2
  17. package/build/components/block-tools/back-compat.js.map +1 -1
  18. package/build/components/block-tools/block-selection-button.js +4 -2
  19. package/build/components/block-tools/block-selection-button.js.map +1 -1
  20. package/build/components/block-tools/index.js +5 -5
  21. package/build/components/block-tools/index.js.map +1 -1
  22. package/build/components/block-tools/insertion-point.js +14 -121
  23. package/build/components/block-tools/insertion-point.js.map +1 -1
  24. package/build/components/block-tools/{block-popover.js → selected-block-popover.js} +25 -108
  25. package/build/components/block-tools/selected-block-popover.js.map +1 -0
  26. package/build/components/duotone-control/index.js +5 -1
  27. package/build/components/duotone-control/index.js.map +1 -1
  28. package/build/components/inserter/index.native.js +30 -8
  29. package/build/components/inserter/index.native.js.map +1 -1
  30. package/build/components/rich-text/index.js +0 -5
  31. package/build/components/rich-text/index.js.map +1 -1
  32. package/build/components/rich-text/index.native.js +0 -4
  33. package/build/components/rich-text/index.native.js.map +1 -1
  34. package/build/components/use-block-display-information/index.js +3 -1
  35. package/build/components/use-block-display-information/index.js.map +1 -1
  36. package/build/hooks/border.js +468 -44
  37. package/build/hooks/border.js.map +1 -1
  38. package/build/hooks/duotone.js +66 -16
  39. package/build/hooks/duotone.js.map +1 -1
  40. package/build/hooks/index.js +8 -2
  41. package/build/hooks/index.js.map +1 -1
  42. package/build/hooks/use-border-props.js +22 -32
  43. package/build/hooks/use-border-props.js.map +1 -1
  44. package/build/index.js +7 -0
  45. package/build/index.js.map +1 -1
  46. package/build/store/actions.js +14 -2
  47. package/build/store/actions.js.map +1 -1
  48. package/build/store/reducer.js +0 -26
  49. package/build/store/reducer.js.map +1 -1
  50. package/build/store/selectors.js +9 -3
  51. package/build/store/selectors.js.map +1 -1
  52. package/build-module/components/block-list/block.native.js +3 -1
  53. package/build-module/components/block-list/block.native.js.map +1 -1
  54. package/build-module/components/block-mover/button.js +5 -5
  55. package/build-module/components/block-mover/button.js.map +1 -1
  56. package/build-module/components/block-mover/index.js +38 -63
  57. package/build-module/components/block-mover/index.js.map +1 -1
  58. package/build-module/components/block-popover/inbetween.js +165 -0
  59. package/build-module/components/block-popover/inbetween.js.map +1 -0
  60. package/build-module/components/block-popover/index.js +67 -0
  61. package/build-module/components/block-popover/index.js.map +1 -0
  62. package/build-module/components/{block-tools → block-popover}/use-popover-scroll.js +3 -1
  63. package/build-module/components/block-popover/use-popover-scroll.js.map +1 -0
  64. package/build-module/components/block-styles/index.js +1 -9
  65. package/build-module/components/block-styles/index.js.map +1 -1
  66. package/build-module/components/block-tools/back-compat.js +1 -1
  67. package/build-module/components/block-tools/back-compat.js.map +1 -1
  68. package/build-module/components/block-tools/block-selection-button.js +3 -2
  69. package/build-module/components/block-tools/block-selection-button.js.map +1 -1
  70. package/build-module/components/block-tools/index.js +3 -3
  71. package/build-module/components/block-tools/index.js.map +1 -1
  72. package/build-module/components/block-tools/insertion-point.js +16 -121
  73. package/build-module/components/block-tools/insertion-point.js.map +1 -1
  74. package/build-module/components/block-tools/{block-popover.js → selected-block-popover.js} +25 -105
  75. package/build-module/components/block-tools/selected-block-popover.js.map +1 -0
  76. package/build-module/components/duotone-control/index.js +4 -1
  77. package/build-module/components/duotone-control/index.js.map +1 -1
  78. package/build-module/components/inserter/index.native.js +31 -10
  79. package/build-module/components/inserter/index.native.js.map +1 -1
  80. package/build-module/components/rich-text/index.js +0 -4
  81. package/build-module/components/rich-text/index.js.map +1 -1
  82. package/build-module/components/rich-text/index.native.js +0 -4
  83. package/build-module/components/rich-text/index.native.js.map +1 -1
  84. package/build-module/components/use-block-display-information/index.js +3 -1
  85. package/build-module/components/use-block-display-information/index.js.map +1 -1
  86. package/build-module/hooks/border.js +458 -44
  87. package/build-module/hooks/border.js.map +1 -1
  88. package/build-module/hooks/duotone.js +63 -16
  89. package/build-module/hooks/duotone.js.map +1 -1
  90. package/build-module/hooks/index.js +2 -1
  91. package/build-module/hooks/index.js.map +1 -1
  92. package/build-module/hooks/use-border-props.js +21 -30
  93. package/build-module/hooks/use-border-props.js.map +1 -1
  94. package/build-module/index.js +1 -1
  95. package/build-module/index.js.map +1 -1
  96. package/build-module/store/actions.js +14 -2
  97. package/build-module/store/actions.js.map +1 -1
  98. package/build-module/store/reducer.js +0 -24
  99. package/build-module/store/reducer.js.map +1 -1
  100. package/build-module/store/selectors.js +8 -3
  101. package/build-module/store/selectors.js.map +1 -1
  102. package/build-style/style-rtl.css +111 -246
  103. package/build-style/style.css +111 -246
  104. package/package.json +28 -28
  105. package/src/components/block-list/block.native.js +2 -0
  106. package/src/components/block-mover/button.js +5 -7
  107. package/src/components/block-mover/index.js +37 -60
  108. package/src/components/block-mover/stories/index.js +110 -0
  109. package/src/components/block-mover/style.scss +48 -138
  110. package/src/components/block-popover/README.md +41 -0
  111. package/src/components/block-popover/inbetween.js +180 -0
  112. package/src/components/block-popover/index.js +73 -0
  113. package/src/components/block-popover/style.scss +24 -0
  114. package/src/components/{block-tools → block-popover}/use-popover-scroll.js +3 -1
  115. package/src/components/block-styles/index.js +1 -12
  116. package/src/components/block-switcher/style.scss +0 -4
  117. package/src/components/block-toolbar/style.scss +0 -12
  118. package/src/components/block-tools/back-compat.js +1 -1
  119. package/src/components/block-tools/block-selection-button.js +3 -1
  120. package/src/components/block-tools/index.js +6 -4
  121. package/src/components/block-tools/insertion-point.js +19 -152
  122. package/src/components/block-tools/{block-popover.js → selected-block-popover.js} +24 -116
  123. package/src/components/block-tools/style.scss +11 -123
  124. package/src/components/border-radius-control/style.scss +5 -2
  125. package/src/components/default-block-appender/style.scss +1 -2
  126. package/src/components/duotone-control/index.js +8 -1
  127. package/src/components/gradients/README.md +29 -0
  128. package/src/components/inserter/index.native.js +60 -25
  129. package/src/components/inserter/style.native.scss +24 -3
  130. package/src/components/navigable-toolbar/README.md +16 -0
  131. package/src/components/rich-text/index.js +0 -2
  132. package/src/components/rich-text/index.native.js +0 -4
  133. package/src/components/use-block-display-information/index.js +2 -0
  134. package/src/hooks/border.js +438 -72
  135. package/src/hooks/border.scss +48 -0
  136. package/src/hooks/duotone.js +98 -62
  137. package/src/hooks/index.js +2 -1
  138. package/src/hooks/use-border-props.js +15 -32
  139. package/src/index.js +1 -0
  140. package/src/store/actions.js +14 -2
  141. package/src/store/reducer.js +0 -21
  142. package/src/store/selectors.js +12 -3
  143. package/src/store/test/actions.js +0 -18
  144. package/src/store/test/reducer.js +0 -19
  145. package/src/store/test/selectors.js +0 -19
  146. package/src/style.scss +1 -1
  147. package/build/components/block-mobile-toolbar/index.js +0 -42
  148. package/build/components/block-mobile-toolbar/index.js.map +0 -1
  149. package/build/components/block-tools/block-popover.js.map +0 -1
  150. package/build/components/block-tools/use-popover-scroll.js.map +0 -1
  151. package/build/components/list-view/appender.js +0 -93
  152. package/build/components/list-view/appender.js.map +0 -1
  153. package/build/components/list-view/list-item.js +0 -62
  154. package/build/components/list-view/list-item.js.map +0 -1
  155. package/build/components/rich-text/use-caret-in-format.js +0 -43
  156. package/build/components/rich-text/use-caret-in-format.js.map +0 -1
  157. package/build/hooks/border-color.js +0 -302
  158. package/build/hooks/border-color.js.map +0 -1
  159. package/build/hooks/border-style.js +0 -96
  160. package/build/hooks/border-style.js.map +0 -1
  161. package/build/hooks/border-width.js +0 -162
  162. package/build/hooks/border-width.js.map +0 -1
  163. package/build-module/components/block-mobile-toolbar/index.js +0 -31
  164. package/build-module/components/block-mobile-toolbar/index.js.map +0 -1
  165. package/build-module/components/block-tools/block-popover.js.map +0 -1
  166. package/build-module/components/block-tools/use-popover-scroll.js.map +0 -1
  167. package/build-module/components/list-view/appender.js +0 -76
  168. package/build-module/components/list-view/appender.js.map +0 -1
  169. package/build-module/components/list-view/list-item.js +0 -47
  170. package/build-module/components/list-view/list-item.js.map +0 -1
  171. package/build-module/components/rich-text/use-caret-in-format.js +0 -33
  172. package/build-module/components/rich-text/use-caret-in-format.js.map +0 -1
  173. package/build-module/hooks/border-color.js +0 -276
  174. package/build-module/hooks/border-color.js.map +0 -1
  175. package/build-module/hooks/border-style.js +0 -78
  176. package/build-module/hooks/border-style.js.map +0 -1
  177. package/build-module/hooks/border-width.js +0 -143
  178. package/build-module/hooks/border-width.js.map +0 -1
  179. package/src/components/block-mobile-toolbar/index.js +0 -24
  180. package/src/components/block-mobile-toolbar/style.scss +0 -29
  181. package/src/components/list-view/appender.js +0 -82
  182. package/src/components/list-view/list-item.js +0 -59
  183. package/src/components/rich-text/use-caret-in-format.js +0 -28
  184. package/src/hooks/border-color.js +0 -315
  185. package/src/hooks/border-style.js +0 -64
  186. package/src/hooks/border-width.js +0 -139
@@ -1,13 +1,34 @@
1
1
  /** @format */
2
2
 
3
- .addBlockButton {
4
- color: $blue-wordpress;
3
+ .inserter-menu__add-block-button-icon {
4
+ color: $blue-50;
5
5
  }
6
6
 
7
- .addBlockButtonDark {
7
+ .inserter-menu__add-block-button-icon--dark {
8
8
  color: $blue-30;
9
9
  }
10
10
 
11
+ .inserter-menu__add-block-button-icon--expanded {
12
+ color: $white;
13
+ }
14
+
15
+ .inserter-menu__add-block-button {
16
+ border-radius: 22px;
17
+ background-color: $blue-50;
18
+ margin: 8px;
19
+ padding: 6px 16px 6px 12px;
20
+ }
21
+
22
+ .inserter-menu__add-block-button--dark {
23
+ background-color: $blue-30;
24
+ }
25
+
26
+ .inserter-menu__add-block-button-text {
27
+ color: $white;
28
+ font-weight: 500;
29
+ align-self: center;
30
+ }
31
+
11
32
  .inserter-menu__list-wrapper {
12
33
  flex: 1;
13
34
  }
@@ -0,0 +1,16 @@
1
+ # NavigableToolbar
2
+
3
+ A toolbar that can be navigated with a keyboard
4
+
5
+ ## Props
6
+
7
+ The component accepts the following props. Props not included in this set will be applied to the element wrapping NavigableMenu content.
8
+
9
+ ## `focusOnMount`
10
+
11
+ Whether to immediately focus when the component mounts.
12
+
13
+ - Type: `Boolean`
14
+ - Required: No
15
+ - Default: false
16
+
@@ -36,7 +36,6 @@ import { useBlockEditContext } from '../block-edit';
36
36
  import FormatToolbarContainer from './format-toolbar-container';
37
37
  import { store as blockEditorStore } from '../../store';
38
38
  import { useUndoAutomaticChange } from './use-undo-automatic-change';
39
- import { useCaretInFormat } from './use-caret-in-format';
40
39
  import { useMarkPersistent } from './use-mark-persistent';
41
40
  import { usePasteHandler } from './use-paste-handler';
42
41
  import { useInputRules } from './use-input-rules';
@@ -268,7 +267,6 @@ function RichTextWrapper(
268
267
  onChange,
269
268
  } );
270
269
 
271
- useCaretInFormat( { value } );
272
270
  useMarkPersistent( { html: adjustedValue, value } );
273
271
 
274
272
  const keyboardShortcuts = useRef( new Set() );
@@ -125,7 +125,6 @@ function RichTextWrapper(
125
125
  const embedHandlerPickerRef = useRef();
126
126
  const selector = ( select ) => {
127
127
  const {
128
- isCaretWithinFormattedText,
129
128
  getSelectionStart,
130
129
  getSelectionEnd,
131
130
  getSettings,
@@ -163,7 +162,6 @@ function RichTextWrapper(
163
162
  }
164
163
 
165
164
  return {
166
- isCaretWithinFormattedText: isCaretWithinFormattedText(),
167
165
  selectionStart: isSelected ? selectionStart.offset : undefined,
168
166
  selectionEnd: isSelected ? selectionEnd.offset : undefined,
169
167
  isSelected,
@@ -177,7 +175,6 @@ function RichTextWrapper(
177
175
  // retrieved from the store on merge.
178
176
  // To do: fix this somehow.
179
177
  const {
180
- isCaretWithinFormattedText,
181
178
  selectionStart,
182
179
  selectionEnd,
183
180
  isSelected,
@@ -600,7 +597,6 @@ function RichTextWrapper(
600
597
  __unstableIsSelected={ isSelected }
601
598
  __unstableInputRule={ inputRule }
602
599
  __unstableMultilineTag={ multilineTag }
603
- __unstableIsCaretWithinFormattedText={ isCaretWithinFormattedText }
604
600
  __unstableOnEnterFormattedText={ enterFormattedText }
605
601
  __unstableOnExitFormattedText={ exitFormattedText }
606
602
  __unstableOnCreateUndoLevel={ __unstableMarkLastChangeAsPersistent }
@@ -19,6 +19,7 @@ import { store as blockEditorStore } from '../../store';
19
19
  * @property {string} title Human-readable block type label.
20
20
  * @property {WPIcon} icon Block type icon.
21
21
  * @property {string} description A detailed block type description.
22
+ * @property {string} anchor HTML anchor.
22
23
  */
23
24
 
24
25
  /**
@@ -63,6 +64,7 @@ export default function useBlockDisplayInformation( clientId ) {
63
64
  title: match.title || blockType.title,
64
65
  icon: match.icon || blockType.icon,
65
66
  description: match.description || blockType.description,
67
+ anchor: attributes?.anchor,
66
68
  };
67
69
  },
68
70
  [ clientId ]
@@ -1,53 +1,174 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import classnames from 'classnames';
5
+
1
6
  /**
2
7
  * WordPress dependencies
3
8
  */
4
9
  import { getBlockSupport } from '@wordpress/blocks';
5
- import { __experimentalToolsPanelItem as ToolsPanelItem } from '@wordpress/components';
10
+ import {
11
+ __experimentalBorderBoxControl as BorderBoxControl,
12
+ __experimentalHasSplitBorders as hasSplitBorders,
13
+ __experimentalIsDefinedBorder as isDefinedBorder,
14
+ __experimentalToolsPanelItem as ToolsPanelItem,
15
+ } from '@wordpress/components';
16
+ import { createHigherOrderComponent } from '@wordpress/compose';
6
17
  import { Platform } from '@wordpress/element';
18
+ import { addFilter } from '@wordpress/hooks';
7
19
  import { __ } from '@wordpress/i18n';
8
20
 
9
21
  /**
10
22
  * Internal dependencies
11
23
  */
12
- import {
13
- BorderColorEdit,
14
- hasBorderColorValue,
15
- resetBorderColor,
16
- } from './border-color';
17
24
  import {
18
25
  BorderRadiusEdit,
19
26
  hasBorderRadiusValue,
20
27
  resetBorderRadius,
21
28
  } from './border-radius';
22
- import {
23
- BorderStyleEdit,
24
- hasBorderStyleValue,
25
- resetBorderStyle,
26
- } from './border-style';
27
- import {
28
- BorderWidthEdit,
29
- hasBorderWidthValue,
30
- resetBorderWidth,
31
- } from './border-width';
29
+ import { getColorClassName } from '../components/colors';
32
30
  import InspectorControls from '../components/inspector-controls';
31
+ import useMultipleOriginColorsAndGradients from '../components/colors-gradients/use-multiple-origin-colors-and-gradients';
33
32
  import useSetting from '../components/use-setting';
34
- import { cleanEmptyObject } from './utils';
33
+ import { cleanEmptyObject, shouldSkipSerialization } from './utils';
35
34
 
36
35
  export const BORDER_SUPPORT_KEY = '__experimentalBorder';
37
36
 
37
+ const borderSides = [ 'top', 'right', 'bottom', 'left' ];
38
+
39
+ const hasBorderValue = ( props ) => {
40
+ const { borderColor, style } = props.attributes;
41
+ return isDefinedBorder( style?.border ) || !! borderColor;
42
+ };
43
+
44
+ // The border color, style, and width are omitted so they get undefined. The
45
+ // border radius is separate and must retain its selection.
46
+ const resetBorder = ( { attributes = {}, setAttributes } ) => {
47
+ const { style } = attributes;
48
+ setAttributes( {
49
+ borderColor: undefined,
50
+ style: {
51
+ ...style,
52
+ border: cleanEmptyObject( {
53
+ radius: style?.border?.radius,
54
+ } ),
55
+ },
56
+ } );
57
+ };
58
+
59
+ const resetBorderFilter = ( newAttributes ) => ( {
60
+ ...newAttributes,
61
+ borderColor: undefined,
62
+ style: {
63
+ ...newAttributes.style,
64
+ border: {
65
+ radius: newAttributes.style?.border?.radius,
66
+ },
67
+ },
68
+ } );
69
+
70
+ const getColorByProperty = ( colors, property, value ) => {
71
+ let matchedColor;
72
+
73
+ colors.some( ( origin ) =>
74
+ origin.colors.some( ( color ) => {
75
+ if ( color[ property ] === value ) {
76
+ matchedColor = color;
77
+ return true;
78
+ }
79
+
80
+ return false;
81
+ } )
82
+ );
83
+
84
+ return matchedColor;
85
+ };
86
+
87
+ export const getMultiOriginColor = ( { colors, namedColor, customColor } ) => {
88
+ // Search each origin (default, theme, or user) for matching color by name.
89
+ if ( namedColor ) {
90
+ const colorObject = getColorByProperty( colors, 'slug', namedColor );
91
+ if ( colorObject ) {
92
+ return colorObject;
93
+ }
94
+ }
95
+
96
+ // Skip if no custom color or matching named color.
97
+ if ( ! customColor ) {
98
+ return { color: undefined };
99
+ }
100
+
101
+ // Attempt to find color via custom color value or build new object.
102
+ const colorObject = getColorByProperty( colors, 'color', customColor );
103
+ return colorObject ? colorObject : { color: customColor };
104
+ };
105
+
106
+ const getBorderObject = ( attributes, colors ) => {
107
+ const { borderColor, style } = attributes;
108
+ const { border: borderStyles } = style || {};
109
+
110
+ // If we have a named color for a flat border. Fetch that color object and
111
+ // apply that color's value to the color property within the style object.
112
+ if ( borderColor ) {
113
+ const { color } = getMultiOriginColor( {
114
+ colors,
115
+ namedColor: borderColor,
116
+ } );
117
+
118
+ return color ? { ...borderStyles, color } : borderStyles;
119
+ }
120
+
121
+ // Individual side border color slugs are stored within the border style
122
+ // object. If we don't have a border styles object we have nothing further
123
+ // to hydrate.
124
+ if ( ! borderStyles ) {
125
+ return borderStyles;
126
+ }
127
+
128
+ // If we have named colors for the individual side borders, retrieve their
129
+ // related color objects and apply the real color values to the split
130
+ // border objects.
131
+ const hydratedBorderStyles = { ...borderStyles };
132
+ borderSides.forEach( ( side ) => {
133
+ const colorSlug = getColorSlugFromVariable(
134
+ hydratedBorderStyles[ side ]?.color
135
+ );
136
+ if ( colorSlug ) {
137
+ const { color } = getMultiOriginColor( {
138
+ colors,
139
+ namedColor: colorSlug,
140
+ } );
141
+ hydratedBorderStyles[ side ] = {
142
+ ...hydratedBorderStyles[ side ],
143
+ color,
144
+ };
145
+ }
146
+ } );
147
+
148
+ return hydratedBorderStyles;
149
+ };
150
+
151
+ function getColorSlugFromVariable( value ) {
152
+ const namedColor = /var:preset\|color\|(.+)/.exec( value );
153
+ if ( namedColor && namedColor[ 1 ] ) {
154
+ return namedColor[ 1 ];
155
+ }
156
+ return null;
157
+ }
158
+
38
159
  export function BorderPanel( props ) {
39
- const { clientId } = props;
160
+ const { attributes, clientId, setAttributes } = props;
161
+ const { style } = attributes;
162
+ const { colors } = useMultipleOriginColorsAndGradients();
40
163
 
164
+ const isSupported = hasBorderSupport( props.name );
41
165
  const isColorSupported =
42
166
  useSetting( 'border.color' ) && hasBorderSupport( props.name, 'color' );
43
-
44
167
  const isRadiusSupported =
45
168
  useSetting( 'border.radius' ) &&
46
169
  hasBorderSupport( props.name, 'radius' );
47
-
48
170
  const isStyleSupported =
49
171
  useSetting( 'border.style' ) && hasBorderSupport( props.name, 'style' );
50
-
51
172
  const isWidthSupported =
52
173
  useSetting( 'border.width' ) && hasBorderSupport( props.name, 'width' );
53
174
 
@@ -58,7 +179,7 @@ export function BorderPanel( props ) {
58
179
  ! isWidthSupported,
59
180
  ].every( Boolean );
60
181
 
61
- if ( isDisabled ) {
182
+ if ( isDisabled || ! isSupported ) {
62
183
  return null;
63
184
  }
64
185
 
@@ -67,61 +188,103 @@ export function BorderPanel( props ) {
67
188
  '__experimentalDefaultControls',
68
189
  ] );
69
190
 
70
- const createResetAllFilter = (
71
- borderAttribute,
72
- topLevelAttributes = {}
73
- ) => ( newAttributes ) => ( {
74
- ...newAttributes,
75
- ...topLevelAttributes,
76
- style: {
77
- ...newAttributes.style,
78
- border: {
79
- ...newAttributes.style?.border,
80
- [ borderAttribute ]: undefined,
81
- },
82
- },
83
- } );
191
+ const showBorderByDefault =
192
+ defaultBorderControls?.color || defaultBorderControls?.width;
193
+
194
+ const onBorderChange = ( newBorder ) => {
195
+ // Filter out named colors and apply them to appropriate block
196
+ // attributes so that CSS classes can be used to apply those colors.
197
+ // e.g. has-primary-border-top-color.
198
+
199
+ let newBorderStyles = { ...newBorder };
200
+ let newBorderColor;
201
+
202
+ if ( hasSplitBorders( newBorder ) ) {
203
+ // For each side check if the side has a color value set
204
+ // If so, determine if it belongs to a named color, in which case
205
+ // we update the color property.
206
+ //
207
+ // This deliberately overwrites `newBorderStyles` to avoid mutating
208
+ // the passed object which causes problems otherwise.
209
+ newBorderStyles = {
210
+ top: { ...newBorder.top },
211
+ right: { ...newBorder.right },
212
+ bottom: { ...newBorder.bottom },
213
+ left: { ...newBorder.left },
214
+ };
215
+
216
+ borderSides.forEach( ( side ) => {
217
+ if ( newBorder[ side ]?.color ) {
218
+ const colorObject = getMultiOriginColor( {
219
+ colors,
220
+ customColor: newBorder[ side ]?.color,
221
+ } );
222
+
223
+ if ( colorObject.slug ) {
224
+ newBorderStyles[
225
+ side
226
+ ].color = `var:preset|color|${ colorObject.slug }`;
227
+ }
228
+ }
229
+ } );
230
+ } else if ( newBorder?.color ) {
231
+ // We have a flat border configuration. Apply named color slug to
232
+ // `borderColor` attribute and clear color style property if found.
233
+ const customColor = newBorder?.color;
234
+ const colorObject = getMultiOriginColor( { colors, customColor } );
235
+
236
+ if ( colorObject.slug ) {
237
+ newBorderColor = colorObject.slug;
238
+ newBorderStyles.color = undefined;
239
+ }
240
+ }
241
+
242
+ // Ensure previous border radius styles are maintained and clean
243
+ // overall result for empty objects or properties.
244
+ const newStyle = cleanEmptyObject( {
245
+ ...style,
246
+ border: { radius: style?.border?.radius, ...newBorderStyles },
247
+ } );
248
+
249
+ setAttributes( {
250
+ style: newStyle,
251
+ borderColor: newBorderColor,
252
+ } );
253
+ };
254
+
255
+ const hydratedBorder = getBorderObject( attributes, colors );
84
256
 
85
257
  return (
86
258
  <InspectorControls __experimentalGroup="border">
87
- { isWidthSupported && (
259
+ { ( isWidthSupported || isColorSupported ) && (
88
260
  <ToolsPanelItem
89
- className="single-column"
90
- hasValue={ () => hasBorderWidthValue( props ) }
91
- label={ __( 'Width' ) }
92
- onDeselect={ () => resetBorderWidth( props ) }
93
- isShownByDefault={ defaultBorderControls?.width }
94
- resetAllFilter={ createResetAllFilter( 'width' ) }
261
+ hasValue={ () => hasBorderValue( props ) }
262
+ label={ __( 'Border' ) }
263
+ onDeselect={ () => resetBorder( props ) }
264
+ isShownByDefault={ showBorderByDefault }
265
+ resetAllFilter={ resetBorderFilter }
95
266
  panelId={ clientId }
96
267
  >
97
- <BorderWidthEdit { ...props } />
98
- </ToolsPanelItem>
99
- ) }
100
- { isStyleSupported && (
101
- <ToolsPanelItem
102
- className="single-column"
103
- hasValue={ () => hasBorderStyleValue( props ) }
104
- label={ __( 'Style' ) }
105
- onDeselect={ () => resetBorderStyle( props ) }
106
- isShownByDefault={ defaultBorderControls?.style }
107
- resetAllFilter={ createResetAllFilter( 'style' ) }
108
- panelId={ clientId }
109
- >
110
- <BorderStyleEdit { ...props } />
111
- </ToolsPanelItem>
112
- ) }
113
- { isColorSupported && (
114
- <ToolsPanelItem
115
- hasValue={ () => hasBorderColorValue( props ) }
116
- label={ __( 'Color' ) }
117
- onDeselect={ () => resetBorderColor( props ) }
118
- isShownByDefault={ defaultBorderControls?.color }
119
- resetAllFilter={ createResetAllFilter( 'color', {
120
- borderColor: undefined,
121
- } ) }
122
- panelId={ clientId }
123
- >
124
- <BorderColorEdit { ...props } />
268
+ <BorderBoxControl
269
+ colors={ colors }
270
+ enableAlpha={ true }
271
+ onChange={ onBorderChange }
272
+ popoverClassNames={ {
273
+ linked: 'block-editor__border-box-control__popover',
274
+ top:
275
+ 'block-editor__border-box-control__popover-top',
276
+ right:
277
+ 'block-editor__border-box-control__popover-right',
278
+ bottom:
279
+ 'block-editor__border-box-control__popover-bottom',
280
+ left:
281
+ 'block-editor__border-box-control__popover-left',
282
+ } }
283
+ showStyle={ isStyleSupported }
284
+ value={ hydratedBorder }
285
+ __experimentalHasMultipleOrigins={ true }
286
+ __experimentalIsRenderedInSidebar={ true }
287
+ />
125
288
  </ToolsPanelItem>
126
289
  ) }
127
290
  { isRadiusSupported && (
@@ -130,7 +293,16 @@ export function BorderPanel( props ) {
130
293
  label={ __( 'Radius' ) }
131
294
  onDeselect={ () => resetBorderRadius( props ) }
132
295
  isShownByDefault={ defaultBorderControls?.radius }
133
- resetAllFilter={ createResetAllFilter( 'radius' ) }
296
+ resetAllFilter={ ( newAttributes ) => ( {
297
+ ...newAttributes,
298
+ style: {
299
+ ...newAttributes.style,
300
+ border: {
301
+ ...newAttributes.style?.border,
302
+ radius: undefined,
303
+ },
304
+ },
305
+ } ) }
134
306
  panelId={ clientId }
135
307
  >
136
308
  <BorderRadiusEdit { ...props } />
@@ -189,3 +361,197 @@ export function removeBorderAttribute( style, attribute ) {
189
361
  },
190
362
  } );
191
363
  }
364
+
365
+ /**
366
+ * Filters registered block settings, extending attributes to include
367
+ * `borderColor` if needed.
368
+ *
369
+ * @param {Object} settings Original block settings.
370
+ *
371
+ * @return {Object} Updated block settings.
372
+ */
373
+ function addAttributes( settings ) {
374
+ if ( ! hasBorderSupport( settings, 'color' ) ) {
375
+ return settings;
376
+ }
377
+
378
+ // Allow blocks to specify default value if needed.
379
+ if ( settings.attributes.borderColor ) {
380
+ return settings;
381
+ }
382
+
383
+ // Add new borderColor attribute to block settings.
384
+ return {
385
+ ...settings,
386
+ attributes: {
387
+ ...settings.attributes,
388
+ borderColor: {
389
+ type: 'string',
390
+ },
391
+ },
392
+ };
393
+ }
394
+
395
+ /**
396
+ * Override props assigned to save component to inject border color.
397
+ *
398
+ * @param {Object} props Additional props applied to save element.
399
+ * @param {Object} blockType Block type definition.
400
+ * @param {Object} attributes Block's attributes.
401
+ *
402
+ * @return {Object} Filtered props to apply to save element.
403
+ */
404
+ function addSaveProps( props, blockType, attributes ) {
405
+ if (
406
+ ! hasBorderSupport( blockType, 'color' ) ||
407
+ shouldSkipSerialization( blockType, BORDER_SUPPORT_KEY, 'color' )
408
+ ) {
409
+ return props;
410
+ }
411
+
412
+ const borderClasses = getBorderClasses( attributes );
413
+ const newClassName = classnames( props.className, borderClasses );
414
+
415
+ // If we are clearing the last of the previous classes in `className`
416
+ // set it to `undefined` to avoid rendering empty DOM attributes.
417
+ props.className = newClassName ? newClassName : undefined;
418
+
419
+ return props;
420
+ }
421
+
422
+ /**
423
+ * Generates a CSS class name consisting of all the applicable border color
424
+ * classes given the current block attributes.
425
+ *
426
+ * @param {Object} attributes Block's attributes.
427
+ *
428
+ * @return {string} CSS class name.
429
+ */
430
+ export function getBorderClasses( attributes ) {
431
+ const { borderColor, style } = attributes;
432
+ const borderColorClass = getColorClassName( 'border-color', borderColor );
433
+
434
+ return classnames( {
435
+ 'has-border-color': borderColor || style?.border?.color,
436
+ [ borderColorClass ]: !! borderColorClass,
437
+ } );
438
+ }
439
+
440
+ /**
441
+ * Filters the registered block settings to apply border color styles and
442
+ * classnames to the block edit wrapper.
443
+ *
444
+ * @param {Object} settings Original block settings.
445
+ *
446
+ * @return {Object} Filtered block settings.
447
+ */
448
+ function addEditProps( settings ) {
449
+ if (
450
+ ! hasBorderSupport( settings, 'color' ) ||
451
+ shouldSkipSerialization( settings, BORDER_SUPPORT_KEY, 'color' )
452
+ ) {
453
+ return settings;
454
+ }
455
+
456
+ const existingGetEditWrapperProps = settings.getEditWrapperProps;
457
+ settings.getEditWrapperProps = ( attributes ) => {
458
+ let props = {};
459
+
460
+ if ( existingGetEditWrapperProps ) {
461
+ props = existingGetEditWrapperProps( attributes );
462
+ }
463
+
464
+ return addSaveProps( props, settings, attributes );
465
+ };
466
+
467
+ return settings;
468
+ }
469
+
470
+ /**
471
+ * This adds inline styles for color palette colors.
472
+ * Ideally, this is not needed and themes should load their palettes on the editor.
473
+ *
474
+ * @param {Function} BlockListBlock Original component.
475
+ *
476
+ * @return {Function} Wrapped component.
477
+ */
478
+ export const withBorderColorPaletteStyles = createHigherOrderComponent(
479
+ ( BlockListBlock ) => ( props ) => {
480
+ const { name, attributes } = props;
481
+ const { borderColor, style } = attributes;
482
+ const { colors } = useMultipleOriginColorsAndGradients();
483
+
484
+ if (
485
+ ! hasBorderSupport( name, 'color' ) ||
486
+ shouldSkipSerialization( name, BORDER_SUPPORT_KEY, 'color' )
487
+ ) {
488
+ return <BlockListBlock { ...props } />;
489
+ }
490
+
491
+ const { color: borderColorValue } = getMultiOriginColor( {
492
+ colors,
493
+ namedColor: borderColor,
494
+ } );
495
+ const { color: borderTopColor } = getMultiOriginColor( {
496
+ colors,
497
+ namedColor: getColorSlugFromVariable( style?.border?.top?.color ),
498
+ } );
499
+ const { color: borderRightColor } = getMultiOriginColor( {
500
+ colors,
501
+ namedColor: getColorSlugFromVariable( style?.border?.right?.color ),
502
+ } );
503
+
504
+ const { color: borderBottomColor } = getMultiOriginColor( {
505
+ colors,
506
+ namedColor: getColorSlugFromVariable(
507
+ style?.border?.bottom?.color
508
+ ),
509
+ } );
510
+ const { color: borderLeftColor } = getMultiOriginColor( {
511
+ colors,
512
+ namedColor: getColorSlugFromVariable( style?.border?.left?.color ),
513
+ } );
514
+
515
+ const extraStyles = {
516
+ borderTopColor: borderTopColor || borderColorValue,
517
+ borderRightColor: borderRightColor || borderColorValue,
518
+ borderBottomColor: borderBottomColor || borderColorValue,
519
+ borderLeftColor: borderLeftColor || borderColorValue,
520
+ };
521
+
522
+ let wrapperProps = props.wrapperProps;
523
+ wrapperProps = {
524
+ ...props.wrapperProps,
525
+ style: {
526
+ ...props.wrapperProps?.style,
527
+ ...extraStyles,
528
+ },
529
+ };
530
+
531
+ return <BlockListBlock { ...props } wrapperProps={ wrapperProps } />;
532
+ }
533
+ );
534
+
535
+ addFilter(
536
+ 'blocks.registerBlockType',
537
+ 'core/border/addAttributes',
538
+ addAttributes
539
+ );
540
+
541
+ addFilter(
542
+ 'blocks.getSaveContent.extraProps',
543
+ 'core/border/addSaveProps',
544
+ addSaveProps
545
+ );
546
+
547
+ addFilter(
548
+ 'blocks.registerBlockType',
549
+ 'core/border/addEditProps',
550
+ addEditProps
551
+ );
552
+
553
+ addFilter(
554
+ 'editor.BlockListBlock',
555
+ 'core/border/with-border-color-palette-styles',
556
+ withBorderColorPaletteStyles
557
+ );