@wordpress/block-editor 11.5.0 → 11.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 (198) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/components/block-list/block-invalid-warning.js +63 -80
  3. package/build/components/block-list/block-invalid-warning.js.map +1 -1
  4. package/build/components/block-settings-menu-controls/index.js +1 -1
  5. package/build/components/block-settings-menu-controls/index.js.map +1 -1
  6. package/build/components/block-switcher/block-transformations-menu.native.js +1 -0
  7. package/build/components/block-switcher/block-transformations-menu.native.js.map +1 -1
  8. package/build/components/convert-to-group-buttons/use-convert-to-group-button-props.js +6 -3
  9. package/build/components/convert-to-group-buttons/use-convert-to-group-button-props.js.map +1 -1
  10. package/build/components/global-styles/border-panel.js +306 -0
  11. package/build/components/global-styles/border-panel.js.map +1 -0
  12. package/build/components/global-styles/hooks.js +57 -3
  13. package/build/components/global-styles/hooks.js.map +1 -1
  14. package/build/components/global-styles/index.js +20 -0
  15. package/build/components/global-styles/index.js.map +1 -1
  16. package/build/components/global-styles/typography-panel.js +62 -15
  17. package/build/components/global-styles/typography-panel.js.map +1 -1
  18. package/build/components/global-styles/use-global-styles-output.js +20 -13
  19. package/build/components/global-styles/use-global-styles-output.js.map +1 -1
  20. package/build/components/inserter/index.js +29 -17
  21. package/build/components/inserter/index.js.map +1 -1
  22. package/build/components/inserter/menu.js +1 -1
  23. package/build/components/inserter/menu.js.map +1 -1
  24. package/build/components/inserter/quick-inserter.js +4 -2
  25. package/build/components/inserter/quick-inserter.js.map +1 -1
  26. package/build/components/inserter/search-results.js +10 -3
  27. package/build/components/inserter/search-results.js.map +1 -1
  28. package/build/components/inserter/tabs.js +1 -1
  29. package/build/components/inserter/tabs.js.map +1 -1
  30. package/build/components/link-control/index.js +1 -1
  31. package/build/components/link-control/index.js.map +1 -1
  32. package/build/components/link-control/search-item.js +5 -2
  33. package/build/components/link-control/search-item.js.map +1 -1
  34. package/build/components/list-view/use-block-selection.js +1 -2
  35. package/build/components/list-view/use-block-selection.js.map +1 -1
  36. package/build/components/off-canvas-editor/appender.js +28 -3
  37. package/build/components/off-canvas-editor/appender.js.map +1 -1
  38. package/build/components/off-canvas-editor/branch.js +5 -3
  39. package/build/components/off-canvas-editor/branch.js.map +1 -1
  40. package/build/components/off-canvas-editor/index.js +9 -7
  41. package/build/components/off-canvas-editor/index.js.map +1 -1
  42. package/build/components/off-canvas-editor/link-ui.js +0 -1
  43. package/build/components/off-canvas-editor/link-ui.js.map +1 -1
  44. package/build/components/provider/use-block-sync.js +17 -3
  45. package/build/components/provider/use-block-sync.js.map +1 -1
  46. package/build/components/rich-text/format-toolbar-container.js +0 -3
  47. package/build/components/rich-text/format-toolbar-container.js.map +1 -1
  48. package/build/hooks/border.js +91 -240
  49. package/build/hooks/border.js.map +1 -1
  50. package/build/hooks/custom-class-name.js +4 -4
  51. package/build/hooks/custom-class-name.js.map +1 -1
  52. package/build/hooks/custom-class-name.native.js +3 -4
  53. package/build/hooks/custom-class-name.native.js.map +1 -1
  54. package/build/hooks/layout.js +19 -22
  55. package/build/hooks/layout.js.map +1 -1
  56. package/build/hooks/supports.js +7 -1
  57. package/build/hooks/supports.js.map +1 -1
  58. package/build/hooks/typography.js +2 -1
  59. package/build/hooks/typography.js.map +1 -1
  60. package/build/hooks/utils.js +27 -1
  61. package/build/hooks/utils.js.map +1 -1
  62. package/build/layouts/constrained.js +6 -2
  63. package/build/layouts/constrained.js.map +1 -1
  64. package/build/private-apis.js +4 -1
  65. package/build/private-apis.js.map +1 -1
  66. package/build/store/actions.js +10 -8
  67. package/build/store/actions.js.map +1 -1
  68. package/build/store/selectors.js +19 -3
  69. package/build/store/selectors.js.map +1 -1
  70. package/build/utils/parse-css-unit-to-px.js +15 -9
  71. package/build/utils/parse-css-unit-to-px.js.map +1 -1
  72. package/build-module/components/block-list/block-invalid-warning.js +66 -78
  73. package/build-module/components/block-list/block-invalid-warning.js.map +1 -1
  74. package/build-module/components/block-settings-menu-controls/index.js +1 -1
  75. package/build-module/components/block-settings-menu-controls/index.js.map +1 -1
  76. package/build-module/components/block-switcher/block-transformations-menu.native.js +1 -0
  77. package/build-module/components/block-switcher/block-transformations-menu.native.js.map +1 -1
  78. package/build-module/components/convert-to-group-buttons/use-convert-to-group-button-props.js +6 -3
  79. package/build-module/components/convert-to-group-buttons/use-convert-to-group-button-props.js.map +1 -1
  80. package/build-module/components/global-styles/border-panel.js +291 -0
  81. package/build-module/components/global-styles/border-panel.js.map +1 -0
  82. package/build-module/components/global-styles/hooks.js +54 -3
  83. package/build-module/components/global-styles/hooks.js.map +1 -1
  84. package/build-module/components/global-styles/index.js +2 -1
  85. package/build-module/components/global-styles/index.js.map +1 -1
  86. package/build-module/components/global-styles/typography-panel.js +62 -16
  87. package/build-module/components/global-styles/typography-panel.js.map +1 -1
  88. package/build-module/components/global-styles/use-global-styles-output.js +20 -13
  89. package/build-module/components/global-styles/use-global-styles-output.js.map +1 -1
  90. package/build-module/components/inserter/index.js +28 -16
  91. package/build-module/components/inserter/index.js.map +1 -1
  92. package/build-module/components/inserter/menu.js +1 -1
  93. package/build-module/components/inserter/menu.js.map +1 -1
  94. package/build-module/components/inserter/quick-inserter.js +4 -2
  95. package/build-module/components/inserter/quick-inserter.js.map +1 -1
  96. package/build-module/components/inserter/search-results.js +10 -3
  97. package/build-module/components/inserter/search-results.js.map +1 -1
  98. package/build-module/components/inserter/tabs.js +1 -1
  99. package/build-module/components/inserter/tabs.js.map +1 -1
  100. package/build-module/components/link-control/index.js +1 -1
  101. package/build-module/components/link-control/index.js.map +1 -1
  102. package/build-module/components/link-control/search-item.js +4 -2
  103. package/build-module/components/link-control/search-item.js.map +1 -1
  104. package/build-module/components/list-view/use-block-selection.js +1 -2
  105. package/build-module/components/list-view/use-block-selection.js.map +1 -1
  106. package/build-module/components/off-canvas-editor/appender.js +28 -4
  107. package/build-module/components/off-canvas-editor/appender.js.map +1 -1
  108. package/build-module/components/off-canvas-editor/branch.js +5 -3
  109. package/build-module/components/off-canvas-editor/branch.js.map +1 -1
  110. package/build-module/components/off-canvas-editor/index.js +9 -7
  111. package/build-module/components/off-canvas-editor/index.js.map +1 -1
  112. package/build-module/components/off-canvas-editor/link-ui.js +0 -1
  113. package/build-module/components/off-canvas-editor/link-ui.js.map +1 -1
  114. package/build-module/components/provider/use-block-sync.js +17 -3
  115. package/build-module/components/provider/use-block-sync.js.map +1 -1
  116. package/build-module/components/rich-text/format-toolbar-container.js +0 -3
  117. package/build-module/components/rich-text/format-toolbar-container.js.map +1 -1
  118. package/build-module/hooks/border.js +93 -240
  119. package/build-module/hooks/border.js.map +1 -1
  120. package/build-module/hooks/custom-class-name.js +4 -4
  121. package/build-module/hooks/custom-class-name.js.map +1 -1
  122. package/build-module/hooks/custom-class-name.native.js +3 -4
  123. package/build-module/hooks/custom-class-name.native.js.map +1 -1
  124. package/build-module/hooks/layout.js +19 -22
  125. package/build-module/hooks/layout.js.map +1 -1
  126. package/build-module/hooks/supports.js +7 -1
  127. package/build-module/hooks/supports.js.map +1 -1
  128. package/build-module/hooks/typography.js +2 -1
  129. package/build-module/hooks/typography.js.map +1 -1
  130. package/build-module/hooks/utils.js +27 -1
  131. package/build-module/hooks/utils.js.map +1 -1
  132. package/build-module/layouts/constrained.js +6 -2
  133. package/build-module/layouts/constrained.js.map +1 -1
  134. package/build-module/private-apis.js +3 -1
  135. package/build-module/private-apis.js.map +1 -1
  136. package/build-module/store/actions.js +10 -8
  137. package/build-module/store/actions.js.map +1 -1
  138. package/build-module/store/selectors.js +17 -3
  139. package/build-module/store/selectors.js.map +1 -1
  140. package/build-module/utils/parse-css-unit-to-px.js +15 -9
  141. package/build-module/utils/parse-css-unit-to-px.js.map +1 -1
  142. package/build-style/style-rtl.css +6 -6
  143. package/build-style/style.css +6 -6
  144. package/package.json +31 -31
  145. package/src/components/block-inspector/style.scss +3 -0
  146. package/src/components/block-list/block-invalid-warning.js +72 -64
  147. package/src/components/block-mover/test/__snapshots__/index.native.js.snap +20 -2
  148. package/src/components/block-preview/test/index.js +0 -2
  149. package/src/components/block-settings-menu-controls/index.js +2 -1
  150. package/src/components/block-styles/style.scss +2 -2
  151. package/src/components/block-switcher/block-transformations-menu.native.js +1 -0
  152. package/src/components/color-palette/test/__snapshots__/control.js.snap +16 -14
  153. package/src/components/convert-to-group-buttons/use-convert-to-group-button-props.js +48 -38
  154. package/src/components/global-styles/border-panel.js +285 -0
  155. package/src/components/global-styles/hooks.js +74 -1
  156. package/src/components/global-styles/index.js +2 -0
  157. package/src/components/global-styles/test/use-global-styles-output.js +1 -1
  158. package/src/components/global-styles/typography-panel.js +48 -1
  159. package/src/components/global-styles/use-global-styles-output.js +13 -13
  160. package/src/components/inserter/index.js +30 -11
  161. package/src/components/inserter/menu.js +0 -1
  162. package/src/components/inserter/quick-inserter.js +2 -0
  163. package/src/components/inserter/search-results.js +7 -1
  164. package/src/components/inserter/style.scss +3 -0
  165. package/src/components/inserter/tabs.js +1 -9
  166. package/src/components/link-control/index.js +1 -1
  167. package/src/components/link-control/search-item.js +3 -1
  168. package/src/components/link-control/style.scss +0 -4
  169. package/src/components/link-control/test/index.js +0 -2
  170. package/src/components/list-view/use-block-selection.js +0 -2
  171. package/src/components/off-canvas-editor/appender.js +31 -5
  172. package/src/components/off-canvas-editor/branch.js +3 -1
  173. package/src/components/off-canvas-editor/index.js +7 -7
  174. package/src/components/off-canvas-editor/link-ui.js +0 -1
  175. package/src/components/provider/use-block-sync.js +21 -4
  176. package/src/components/rich-text/format-toolbar-container.js +1 -7
  177. package/src/components/url-popover/test/index.js +0 -2
  178. package/src/hooks/border.js +94 -225
  179. package/src/hooks/custom-class-name.js +4 -4
  180. package/src/hooks/custom-class-name.native.js +3 -4
  181. package/src/hooks/layout.js +19 -16
  182. package/src/hooks/supports.js +6 -0
  183. package/src/hooks/test/style.js +2 -1
  184. package/src/hooks/test/use-typography-props.js +2 -0
  185. package/src/hooks/typography.js +2 -0
  186. package/src/hooks/utils.js +36 -0
  187. package/src/layouts/constrained.js +23 -17
  188. package/src/private-apis.js +2 -0
  189. package/src/store/actions.js +10 -8
  190. package/src/store/selectors.js +20 -3
  191. package/src/utils/parse-css-unit-to-px.js +14 -9
  192. package/src/utils/test/parse-css-unit-to-px.js +1 -2
  193. package/tsconfig.tsbuildinfo +1 -1
  194. package/build/hooks/border-radius.js +0 -100
  195. package/build/hooks/border-radius.js.map +0 -1
  196. package/build-module/hooks/border-radius.js +0 -84
  197. package/build-module/hooks/border-radius.js.map +0 -1
  198. package/src/hooks/border-radius.js +0 -70
@@ -25,59 +25,69 @@ import { store as blockEditorStore } from '../../store';
25
25
  * It is used in `BlockSettingsMenuControls` to know if `ConvertToGroupButton`
26
26
  * should be rendered, to avoid ending up with an empty MenuGroup.
27
27
  *
28
+ * @param {?string[]} selectedClientIds An optional array of clientIds to group. The selected blocks
29
+ * from the block editor store are used if this is not provided.
30
+ *
28
31
  * @return {ConvertToGroupButtonProps} Returns the properties needed by `ConvertToGroupButton`.
29
32
  */
30
- export default function useConvertToGroupButtonProps() {
33
+ export default function useConvertToGroupButtonProps( selectedClientIds ) {
31
34
  const {
32
35
  clientIds,
33
36
  isGroupable,
34
37
  isUngroupable,
35
38
  blocksSelection,
36
39
  groupingBlockName,
37
- } = useSelect( ( select ) => {
38
- const {
39
- getBlockRootClientId,
40
- getBlocksByClientId,
41
- canInsertBlockType,
42
- getSelectedBlockClientIds,
43
- } = select( blockEditorStore );
44
- const { getGroupingBlockName } = select( blocksStore );
40
+ } = useSelect(
41
+ ( select ) => {
42
+ const {
43
+ getBlockRootClientId,
44
+ getBlocksByClientId,
45
+ canInsertBlockType,
46
+ getSelectedBlockClientIds,
47
+ } = select( blockEditorStore );
48
+ const { getGroupingBlockName } = select( blocksStore );
49
+
50
+ const _clientIds = selectedClientIds?.length
51
+ ? selectedClientIds
52
+ : getSelectedBlockClientIds();
53
+ const _groupingBlockName = getGroupingBlockName();
45
54
 
46
- const _clientIds = getSelectedBlockClientIds();
47
- const _groupingBlockName = getGroupingBlockName();
55
+ const rootClientId = !! _clientIds?.length
56
+ ? getBlockRootClientId( _clientIds[ 0 ] )
57
+ : undefined;
48
58
 
49
- const rootClientId = !! _clientIds?.length
50
- ? getBlockRootClientId( _clientIds[ 0 ] )
51
- : undefined;
59
+ const groupingBlockAvailable = canInsertBlockType(
60
+ _groupingBlockName,
61
+ rootClientId
62
+ );
52
63
 
53
- const groupingBlockAvailable = canInsertBlockType(
54
- _groupingBlockName,
55
- rootClientId
56
- );
64
+ const _blocksSelection = getBlocksByClientId( _clientIds );
57
65
 
58
- const _blocksSelection = getBlocksByClientId( _clientIds );
66
+ const isSingleGroupingBlock =
67
+ _blocksSelection.length === 1 &&
68
+ _blocksSelection[ 0 ]?.name === _groupingBlockName;
59
69
 
60
- const isSingleGroupingBlock =
61
- _blocksSelection.length === 1 &&
62
- _blocksSelection[ 0 ]?.name === _groupingBlockName;
70
+ // Do we have
71
+ // 1. Grouping block available to be inserted?
72
+ // 2. One or more blocks selected
73
+ const _isGroupable =
74
+ groupingBlockAvailable && _blocksSelection.length;
63
75
 
64
- // Do we have
65
- // 1. Grouping block available to be inserted?
66
- // 2. One or more blocks selected
67
- const _isGroupable = groupingBlockAvailable && _blocksSelection.length;
76
+ // Do we have a single Group Block selected and does that group have inner blocks?
77
+ const _isUngroupable =
78
+ isSingleGroupingBlock &&
79
+ !! _blocksSelection[ 0 ].innerBlocks.length;
80
+ return {
81
+ clientIds: _clientIds,
82
+ isGroupable: _isGroupable,
83
+ isUngroupable: _isUngroupable,
84
+ blocksSelection: _blocksSelection,
85
+ groupingBlockName: _groupingBlockName,
86
+ };
87
+ },
88
+ [ selectedClientIds ]
89
+ );
68
90
 
69
- // Do we have a single Group Block selected and does that group have inner blocks?
70
- const _isUngroupable =
71
- isSingleGroupingBlock &&
72
- !! _blocksSelection[ 0 ].innerBlocks.length;
73
- return {
74
- clientIds: _clientIds,
75
- isGroupable: _isGroupable,
76
- isUngroupable: _isUngroupable,
77
- blocksSelection: _blocksSelection,
78
- groupingBlockName: _groupingBlockName,
79
- };
80
- }, [] );
81
91
  return {
82
92
  clientIds,
83
93
  isGroupable,
@@ -0,0 +1,285 @@
1
+ /**
2
+ * WordPress dependencies
3
+ */
4
+ import {
5
+ __experimentalBorderBoxControl as BorderBoxControl,
6
+ __experimentalHasSplitBorders as hasSplitBorders,
7
+ __experimentalIsDefinedBorder as isDefinedBorder,
8
+ __experimentalToolsPanel as ToolsPanel,
9
+ __experimentalToolsPanelItem as ToolsPanelItem,
10
+ } from '@wordpress/components';
11
+ import { useCallback, useMemo } from '@wordpress/element';
12
+ import { __ } from '@wordpress/i18n';
13
+
14
+ /**
15
+ * Internal dependencies
16
+ */
17
+ import BorderRadiusControl from '../border-radius-control';
18
+ import { useColorsPerOrigin } from './hooks';
19
+ import { getValueFromVariable } from './utils';
20
+
21
+ export function useHasBorderPanel( settings ) {
22
+ const controls = [
23
+ useHasBorderColorControl( settings ),
24
+ useHasBorderRadiusControl( settings ),
25
+ useHasBorderStyleControl( settings ),
26
+ useHasBorderWidthControl( settings ),
27
+ ];
28
+
29
+ return controls.some( Boolean );
30
+ }
31
+
32
+ function useHasBorderColorControl( settings ) {
33
+ return settings?.border?.color;
34
+ }
35
+
36
+ function useHasBorderRadiusControl( settings ) {
37
+ return settings?.border?.radius;
38
+ }
39
+
40
+ function useHasBorderStyleControl( settings ) {
41
+ return settings?.border?.style;
42
+ }
43
+
44
+ function useHasBorderWidthControl( settings ) {
45
+ return settings?.border?.width;
46
+ }
47
+
48
+ function applyFallbackStyle( border ) {
49
+ if ( ! border ) {
50
+ return border;
51
+ }
52
+
53
+ if ( ! border.style && ( border.color || border.width ) ) {
54
+ return { ...border, style: 'solid' };
55
+ }
56
+
57
+ return border;
58
+ }
59
+
60
+ function applyAllFallbackStyles( border ) {
61
+ if ( ! border ) {
62
+ return border;
63
+ }
64
+
65
+ if ( hasSplitBorders( border ) ) {
66
+ return {
67
+ top: applyFallbackStyle( border.top ),
68
+ right: applyFallbackStyle( border.right ),
69
+ bottom: applyFallbackStyle( border.bottom ),
70
+ left: applyFallbackStyle( border.left ),
71
+ };
72
+ }
73
+
74
+ return applyFallbackStyle( border );
75
+ }
76
+
77
+ function BorderToolsPanel( {
78
+ resetAllFilter,
79
+ onChange,
80
+ value,
81
+ panelId,
82
+ children,
83
+ } ) {
84
+ const resetAll = () => {
85
+ const updatedValue = resetAllFilter( value );
86
+ onChange( updatedValue );
87
+ };
88
+
89
+ return (
90
+ <ToolsPanel
91
+ label={ __( 'Border' ) }
92
+ resetAll={ resetAll }
93
+ panelId={ panelId }
94
+ >
95
+ { children }
96
+ </ToolsPanel>
97
+ );
98
+ }
99
+
100
+ const DEFAULT_CONTROLS = {
101
+ radius: true,
102
+ color: true,
103
+ width: true,
104
+ };
105
+
106
+ export default function BorderPanel( {
107
+ as: Wrapper = BorderToolsPanel,
108
+ value,
109
+ onChange,
110
+ inheritedValue = value,
111
+ settings,
112
+ panelId,
113
+ defaultControls = DEFAULT_CONTROLS,
114
+ } ) {
115
+ const colors = useColorsPerOrigin( settings );
116
+ const decodeValue = ( rawValue ) =>
117
+ getValueFromVariable( { settings }, '', rawValue );
118
+ const encodeColorValue = ( colorValue ) => {
119
+ const allColors = colors.flatMap(
120
+ ( { colors: originColors } ) => originColors
121
+ );
122
+ const colorObject = allColors.find(
123
+ ( { color } ) => color === colorValue
124
+ );
125
+ return colorObject
126
+ ? 'var:preset|color|' + colorObject.slug
127
+ : colorValue;
128
+ };
129
+ const decodeColorValue = useCallback(
130
+ ( colorValue ) => {
131
+ const allColors = colors.flatMap(
132
+ ( { colors: originColors } ) => originColors
133
+ );
134
+ const colorObject = allColors.find(
135
+ ( { slug } ) => colorValue === 'var:preset|color|' + slug
136
+ );
137
+ return colorObject ? colorObject.color : colorValue;
138
+ },
139
+ [ colors ]
140
+ );
141
+ const border = useMemo( () => {
142
+ if ( hasSplitBorders( inheritedValue?.border ) ) {
143
+ const borderValue = { ...inheritedValue?.border };
144
+ [ 'top', 'right', 'bottom', 'left' ].forEach( ( side ) => {
145
+ borderValue[ side ] = {
146
+ ...borderValue[ side ],
147
+ color: decodeColorValue( borderValue[ side ]?.color ),
148
+ };
149
+ } );
150
+ return borderValue;
151
+ }
152
+ return {
153
+ ...inheritedValue?.border,
154
+ color: inheritedValue?.border?.color
155
+ ? decodeColorValue( inheritedValue?.border?.color )
156
+ : undefined,
157
+ };
158
+ }, [ inheritedValue?.border, decodeColorValue ] );
159
+ const setBorder = ( newBorder ) =>
160
+ onChange( { ...value, border: newBorder } );
161
+ const showBorderColor = useHasBorderColorControl( settings );
162
+ const showBorderStyle = useHasBorderStyleControl( settings );
163
+ const showBorderWidth = useHasBorderWidthControl( settings );
164
+
165
+ // Border radius.
166
+ const showBorderRadius = useHasBorderRadiusControl( settings );
167
+ const borderRadiusValues = decodeValue( border?.radius );
168
+ const setBorderRadius = ( newBorderRadius ) =>
169
+ setBorder( { ...border, radius: newBorderRadius } );
170
+ const hasBorderRadius = () => {
171
+ const borderValues = value?.border?.radius;
172
+ if ( typeof borderValues === 'object' ) {
173
+ return Object.entries( borderValues ).some( Boolean );
174
+ }
175
+ return !! borderValues;
176
+ };
177
+
178
+ const resetBorder = () => {
179
+ if ( hasBorderRadius() ) {
180
+ return setBorder( { radius: value?.border?.radius } );
181
+ }
182
+
183
+ setBorder( undefined );
184
+ };
185
+
186
+ const onBorderChange = ( newBorder ) => {
187
+ // Ensure we have a visible border style when a border width or
188
+ // color is being selected.
189
+ const newBorderWithStyle = applyAllFallbackStyles( newBorder );
190
+
191
+ // As we can't conditionally generate styles based on if other
192
+ // style properties have been set we need to force split border
193
+ // definitions for user set border styles. Border radius is derived
194
+ // from the same property i.e. `border.radius` if it is a string
195
+ // that is used. The longhand border radii styles are only generated
196
+ // if that property is an object.
197
+ //
198
+ // For borders (color, style, and width) those are all properties on
199
+ // the `border` style property. This means if the theme.json defined
200
+ // split borders and the user condenses them into a flat border or
201
+ // vice-versa we'd get both sets of styles which would conflict.
202
+ const updatedBorder = ! hasSplitBorders( newBorderWithStyle )
203
+ ? {
204
+ top: newBorderWithStyle,
205
+ right: newBorderWithStyle,
206
+ bottom: newBorderWithStyle,
207
+ left: newBorderWithStyle,
208
+ }
209
+ : {
210
+ color: null,
211
+ style: null,
212
+ width: null,
213
+ ...newBorderWithStyle,
214
+ };
215
+
216
+ [ 'top', 'right', 'bottom', 'left' ].forEach( ( side ) => {
217
+ updatedBorder[ side ] = {
218
+ ...updatedBorder[ side ],
219
+ color: encodeColorValue( updatedBorder[ side ]?.color ),
220
+ };
221
+ } );
222
+
223
+ // As radius is maintained separately to color, style, and width
224
+ // maintain its value. Undefined values here will be cleaned when
225
+ // global styles are saved.
226
+ setBorder( { radius: border?.radius, ...updatedBorder } );
227
+ };
228
+
229
+ const resetAllFilter = useCallback( ( previousValue ) => {
230
+ return {
231
+ ...previousValue,
232
+ border: undefined,
233
+ };
234
+ }, [] );
235
+
236
+ const showBorderByDefault =
237
+ defaultControls?.color || defaultControls?.width;
238
+
239
+ return (
240
+ <Wrapper
241
+ resetAllFilter={ resetAllFilter }
242
+ value={ value }
243
+ onChange={ onChange }
244
+ panelId={ panelId }
245
+ >
246
+ { ( showBorderWidth || showBorderColor ) && (
247
+ <ToolsPanelItem
248
+ hasValue={ () => isDefinedBorder( value?.border ) }
249
+ label={ __( 'Border' ) }
250
+ onDeselect={ () => resetBorder() }
251
+ isShownByDefault={ showBorderByDefault }
252
+ panelId={ panelId }
253
+ >
254
+ <BorderBoxControl
255
+ colors={ colors }
256
+ enableAlpha={ true }
257
+ enableStyle={ showBorderStyle }
258
+ onChange={ onBorderChange }
259
+ popoverOffset={ 40 }
260
+ popoverPlacement="left-start"
261
+ value={ border }
262
+ __experimentalIsRenderedInSidebar={ true }
263
+ size={ '__unstable-large' }
264
+ />
265
+ </ToolsPanelItem>
266
+ ) }
267
+ { showBorderRadius && (
268
+ <ToolsPanelItem
269
+ hasValue={ hasBorderRadius }
270
+ label={ __( 'Radius' ) }
271
+ onDeselect={ () => setBorderRadius( undefined ) }
272
+ isShownByDefault={ defaultControls.radius }
273
+ panelId={ panelId }
274
+ >
275
+ <BorderRadiusControl
276
+ values={ borderRadiusValues }
277
+ onChange={ ( newValue ) => {
278
+ setBorderRadius( newValue || undefined );
279
+ } }
280
+ />
281
+ </ToolsPanelItem>
282
+ ) }
283
+ </Wrapper>
284
+ );
285
+ }
@@ -10,6 +10,7 @@ import { get, set } from 'lodash';
10
10
  import { useContext, useCallback, useMemo } from '@wordpress/element';
11
11
  import { useSelect } from '@wordpress/data';
12
12
  import { store as blocksStore } from '@wordpress/blocks';
13
+ import { _x } from '@wordpress/i18n';
13
14
 
14
15
  /**
15
16
  * Internal dependencies
@@ -64,6 +65,7 @@ const VALID_SETTINGS = [
64
65
  'typography.fontWeight',
65
66
  'typography.letterSpacing',
66
67
  'typography.lineHeight',
68
+ 'typography.textColumns',
67
69
  'typography.textDecoration',
68
70
  'typography.textTransform',
69
71
  ];
@@ -177,7 +179,7 @@ export function useGlobalStyle(
177
179
  switch ( source ) {
178
180
  case 'all':
179
181
  rawResult =
180
- // The stlyes.css path is allowed to be empty, so don't revert to base if undefined.
182
+ // The styles.css path is allowed to be empty, so don't revert to base if undefined.
181
183
  finalPath === 'styles.css'
182
184
  ? get( userConfig, finalPath )
183
185
  : get( mergedConfig, finalPath );
@@ -265,6 +267,16 @@ export function useSettingsForBlockElement(
265
267
  }
266
268
  } );
267
269
 
270
+ // The column-count style is named text column to reduce confusion with
271
+ // the columns block and manage expectations from the support.
272
+ // See: https://github.com/WordPress/gutenberg/pull/33587
273
+ if ( ! supportedStyles.includes( 'columnCount' ) ) {
274
+ updatedSettings.typography = {
275
+ ...updatedSettings.typography,
276
+ textColumns: false,
277
+ };
278
+ }
279
+
268
280
  [ 'contentSize', 'wideSize' ].forEach( ( key ) => {
269
281
  if ( ! supportedStyles.includes( key ) ) {
270
282
  updatedSettings.layout = {
@@ -303,6 +315,67 @@ export function useSettingsForBlockElement(
303
315
  };
304
316
  }
305
317
 
318
+ [ 'radius', 'color', 'style', 'width' ].forEach( ( key ) => {
319
+ if (
320
+ ! supportedStyles.includes(
321
+ 'border' + key.charAt( 0 ).toUpperCase() + key.slice( 1 )
322
+ )
323
+ ) {
324
+ updatedSettings.border = {
325
+ ...updatedSettings.border,
326
+ [ key ]: false,
327
+ };
328
+ }
329
+ } );
330
+
306
331
  return updatedSettings;
307
332
  }, [ parentSettings, supportedStyles, supports ] );
308
333
  }
334
+
335
+ export function useColorsPerOrigin( settings ) {
336
+ const customColors = settings?.color?.palette?.custom;
337
+ const themeColors = settings?.color?.palette?.theme;
338
+ const defaultColors = settings?.color?.palette?.default;
339
+ const shouldDisplayDefaultColors = settings?.color?.defaultPalette;
340
+
341
+ return useMemo( () => {
342
+ const result = [];
343
+ if ( themeColors && themeColors.length ) {
344
+ result.push( {
345
+ name: _x(
346
+ 'Theme',
347
+ 'Indicates this palette comes from the theme.'
348
+ ),
349
+ colors: themeColors,
350
+ } );
351
+ }
352
+ if (
353
+ shouldDisplayDefaultColors &&
354
+ defaultColors &&
355
+ defaultColors.length
356
+ ) {
357
+ result.push( {
358
+ name: _x(
359
+ 'Default',
360
+ 'Indicates this palette comes from WordPress.'
361
+ ),
362
+ colors: defaultColors,
363
+ } );
364
+ }
365
+ if ( customColors && customColors.length ) {
366
+ result.push( {
367
+ name: _x(
368
+ 'Custom',
369
+ 'Indicates this palette is created by the user.'
370
+ ),
371
+ colors: customColors,
372
+ } );
373
+ }
374
+ return result;
375
+ }, [
376
+ customColors,
377
+ themeColors,
378
+ defaultColors,
379
+ shouldDisplayDefaultColors,
380
+ ] );
381
+ }
@@ -3,6 +3,7 @@ export {
3
3
  useGlobalSetting,
4
4
  useGlobalStyle,
5
5
  useSettingsForBlockElement,
6
+ useColorsPerOrigin,
6
7
  } from './hooks';
7
8
  export { useGlobalStylesOutput } from './use-global-styles-output';
8
9
  export { GlobalStylesContext } from './context';
@@ -14,3 +15,4 @@ export {
14
15
  default as DimensionsPanel,
15
16
  useHasDimensionsPanel,
16
17
  } from './dimensions-panel';
18
+ export { default as BorderPanel, useHasBorderPanel } from './border-panel';
@@ -644,7 +644,7 @@ describe( 'global styles renderer', () => {
644
644
  } );
645
645
 
646
646
  expect( layoutStyles ).toEqual(
647
- '.wp-block-group.is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }.wp-block-group.is-layout-flow > * + * { margin-block-start: 12px; margin-block-end: 0; }.wp-block-group.is-layout-flex { gap: 12px; }'
647
+ '.wp-block-group-is-layout-flow.wp-block-group-is-layout-flow > * { margin-block-start: 0; margin-block-end: 0; }.wp-block-group-is-layout-flow.wp-block-group-is-layout-flow > * + * { margin-block-start: 12px; margin-block-end: 0; }.wp-block-group-is-layout-flex.wp-block-group-is-layout-flex { gap: 12px; }'
648
648
  );
649
649
  } );
650
650
 
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import {
5
5
  FontSizePicker,
6
+ __experimentalNumberControl as NumberControl,
6
7
  __experimentalToolsPanel as ToolsPanel,
7
8
  __experimentalToolsPanelItem as ToolsPanelItem,
8
9
  } from '@wordpress/components';
@@ -20,6 +21,9 @@ import TextTransformControl from '../text-transform-control';
20
21
  import TextDecorationControl from '../text-decoration-control';
21
22
  import { getValueFromVariable } from './utils';
22
23
 
24
+ const MIN_TEXT_COLUMNS = 1;
25
+ const MAX_TEXT_COLUMNS = 6;
26
+
23
27
  export function useHasTypographyPanel( settings ) {
24
28
  const hasFontFamily = useHasFontFamilyControl( settings );
25
29
  const hasLineHeight = useHasLineHeightControl( settings );
@@ -27,6 +31,7 @@ export function useHasTypographyPanel( settings ) {
27
31
  const hasLetterSpacing = useHasLetterSpacingControl( settings );
28
32
  const hasTextTransform = useHasTextTransformControl( settings );
29
33
  const hasTextDecoration = useHasTextDecorationControl( settings );
34
+ const hasTextColumns = useHasTextColumnsControl( settings );
30
35
  const hasFontSize = useHasFontSizeControl( settings );
31
36
 
32
37
  return (
@@ -36,7 +41,8 @@ export function useHasTypographyPanel( settings ) {
36
41
  hasLetterSpacing ||
37
42
  hasTextTransform ||
38
43
  hasFontSize ||
39
- hasTextDecoration
44
+ hasTextDecoration ||
45
+ hasTextColumns
40
46
  );
41
47
  }
42
48
 
@@ -93,6 +99,10 @@ function useHasTextDecorationControl( settings ) {
93
99
  return settings?.typography?.textDecoration;
94
100
  }
95
101
 
102
+ function useHasTextColumnsControl( settings ) {
103
+ return settings?.typography?.textColumns;
104
+ }
105
+
96
106
  function TypographyToolsPanel( {
97
107
  resetAllFilter,
98
108
  onChange,
@@ -124,6 +134,7 @@ const DEFAULT_CONTROLS = {
124
134
  letterSpacing: true,
125
135
  textTransform: true,
126
136
  textDecoration: true,
137
+ textColumns: true,
127
138
  };
128
139
 
129
140
  export default function TypographyPanel( {
@@ -246,6 +257,21 @@ export default function TypographyPanel( {
246
257
  const hasLetterSpacing = () => !! value?.typography?.letterSpacing;
247
258
  const resetLetterSpacing = () => setLetterSpacing( undefined );
248
259
 
260
+ // Text Columns
261
+ const hasTextColumnsControl = useHasTextColumnsControl( settings );
262
+ const textColumns = decodeValue( inheritedValue?.typography?.textColumns );
263
+ const setTextColumns = ( newValue ) => {
264
+ onChange( {
265
+ ...value,
266
+ typography: {
267
+ ...value?.typography,
268
+ textColumns: newValue,
269
+ },
270
+ } );
271
+ };
272
+ const hasTextColumns = () => !! value?.typography?.textColumns;
273
+ const resetTextColumns = () => setTextColumns( undefined );
274
+
249
275
  // Text Transform
250
276
  const hasTextTransformControl = useHasTextTransformControl( settings );
251
277
  const textTransform = decodeValue(
@@ -388,6 +414,27 @@ export default function TypographyPanel( {
388
414
  />
389
415
  </ToolsPanelItem>
390
416
  ) }
417
+ { hasTextColumnsControl && (
418
+ <ToolsPanelItem
419
+ className="single-column"
420
+ label={ __( 'Text columns' ) }
421
+ hasValue={ hasTextColumns }
422
+ onDeselect={ resetTextColumns }
423
+ isShownByDefault={ defaultControls.textColumns }
424
+ panelId={ panelId }
425
+ >
426
+ <NumberControl
427
+ label={ __( 'Text columns' ) }
428
+ max={ MAX_TEXT_COLUMNS }
429
+ min={ MIN_TEXT_COLUMNS }
430
+ onChange={ setTextColumns }
431
+ size="__unstable-large"
432
+ spinControls="custom"
433
+ value={ textColumns }
434
+ initialPosition={ 1 }
435
+ />
436
+ </ToolsPanelItem>
437
+ ) }
391
438
  { hasTextDecorationControl && (
392
439
  <ToolsPanelItem
393
440
  className="single-column"